1
0
Fork 0
lasertank-js/src/TileMasker.js

450 lines
16 KiB
JavaScript
Raw Normal View History

Initial commit illustrating LTG loading and masking This project -- creating a LaserTank clone in JavaScript -- deserves some discussion before diving into the implementation details (you may skip to the separator below for implementation details without the history). LaserTank is a game that has a special place in my heart. Not only is it a witty and enjoyable game, but it is in fact the reason I began programming as a young boy in the first place. While it is likely that I would have eventually picked up a programming book for some other reason, I owe that point in time entirely to this game. Allow me to explain. At the age of 10, I would spend much of my time on the Internet downloading various games and demos that may satisfy my interests (the old days of CNET's download.com showed me many of those, I believe). One of those games that immediately captivated me was LaserTank, but not purely for reasons of gameplay. It had this wonderful feature that added so much potential (and replayability) to the game -- a map editor. I found myself enthralled with the map editor to the point where I spent all my time creating maps rather than playing the game. What fascinated me was the ability to essentially create portions of the game -- to tell the game what to do and how to function. I was not able to create my own game using this editor, but I felt like I was creating portions of it. That said, I soon realized that it wasn't enough; I needed to do more. It was the limitations of the map editor and the enjoyment of creating the maps that caused me to convince my parents to take me to Barnes and Noble to purchase my first programming book - Learn to Program with Visual Basic 6 by John Smiley (N.B. Visual Basic is proprietary and I cannot recommend using it. I had no knowledge of the evils of proprietary software back at that point in time.) My parents had their doubts, but that only pushed me even harder to learn. Ironically, the book was about creating a business application. I found this process very enjoyable and began focusing more on conventional desktop software rather than gaming. I did create a couple games (bop-a-mole and breakout among others), but my focus was never game development. Eventually, I moved on from Visual Basic and got into web development. Following that, I discovered GNU/Linux and began getting more and more into lower-level systems and languages (such as C and ASM) and began adopting the hacker ethic. That brings us to where I am today -- nearly 13 years later. It would only seem fitting to bring my hobby-turned-career full circle by cloning the very game that started everything. When I say "clone", I mean nothing more; there will be no additional features or modifications to the original gameplay, menus, graphics, etc. It will support all original file formats (I will develop none of my own). The only differences between the clone and the original game will arise from the obvious issues introduced by cloning the game on a web platform. Specifically, the user will have the option to load files from either their local box or a remote resource, and I may provide pre-masked tile sets for browsers that do not support the canvas element (a fallback mode, if you will). No matter what the change, though, the gameplay will remain identical. That said, the library resulting from the clone will be built with extensibility in mind. If a user (or myself) wishes to create a derivative work by hooking or extending the library (for example, to support larger maps, add additional blocks/enemies, multiplayer support, etc), that should be fairly trivial to do. However, those works will be entirely separate from the clone and clearly distinguished. I think the original LaserTank is perfect the way it is. Remember, it has a special place in my heart (aww) and I would like to preserve the game as I remember it back then. --- Alright; now that we have a great deal of unnecessary history out of the way, let's get into the implementation details for this commit (if you're reading this as a blog entry, see the first commit). This commit represents a proof-of-concept showing that the LTG files (containing the LaserTank graphics) can be properly loaded and their masks properly applied. This was the first major concern for the project and, if a workaround were needed, would have prevented me from creating a full clone (as it would not support loading LTG files without having them first sent to the server, processed, and returned in a different format). The LTG file contains some metadata (including the name, author and description) as well as two bitmaps (BMPs) -- the game tileset and the associated mask. The game bitmap's position was static, but its length and the offset of the mask bitmap were provided by the four bytes immediately preceding the game bitmap (the TLTGREC struct in LTANK.H of the original sources represented this value as a DWORD, which represents a 32-bit integer). The only challenge with converting this value into an integer that we could use is its endianness -- is the most significant byte at the beginning or end? Windows programs (of which LaserTank is) generally write in little-endian format, but to be sure we can simply open up the LTG file in your favorite HEX editor (I simply use `xxd`). In the case of the original tileset, the four bytes immediately preceding the bitmap header (as identified by 'BM', or `424d`) were `7a f5 00 00`, which on its own clearly indicates little-endianness. We can verify by searching for 'BM' once again, and finding that it begins at location `f5 7a` (if your HEX editor displays in big-endian format). To convert into a number, we can simply add up each byte individually, left-shifting by 8N bits, where N is the 0-indexed byte position. Loading the BMP files was then fairly trivial; the file could be loaded into memory (read from disk using FileReader) and we could cut the relevant portions of the binary string out. The bitmaps could then be base64-encoded and the "src" attribute of an Image object set to 'data:image/bmp;base64,B', where B is the base64-encoded BMP. This could then be rendered however we please - CSS sprites or to a canvas. The problem with CSS sprites is that we need to apply the mask and there is no reliable way to do this without a canvas; transparency in the browser is normally handled using GIFs or PNGs. As it turns out, the canvas performs masking using the alpha channel as well, so I would have to create my own masking algorithm to manipulate the alpha channel of the tileset. To complicate matters even more, certain tiles had no mask, and they did not consistently represent the mask with all black (black is used in LT to indicate opacity), meaning that the algorithm would have to understand what tiles should be skipped entirely. The solution was to simply loop through each tile and set the alpha byte of each pixel relative to the respective pixel on the map. Because the images were created out of data in memory, the canvas is not tainted when the image is rendered before using getImageData(). To help speed up the process, since we know that the mask can only contain black and white, we need only check one of the channels; we do not need to calculate brightness. This process successfully returns each individual tile, properly masked, which can be rendered to the canvas using putImageData(). Crisis averted. With that major concern out of the way, the clone should no longer be a problem. I go into more detail in the comments within LtgLoader and TileMasker. This is going to be an exciting process, both because of the LT clone itself and because this is my first experience working with the canvas element. As aforementioned, I seldom create games and I have had no use for the canvas thus far. Hopefully this project will be well-received by both those who have played LaserTank in the past and the original developer of the game (Jim Kindley, JEK Software). LaserTank is a fairly old game and is not likely to be well known anymore, but the game itself is a blast (no pun intended) and bringing it to the browser, where it can be used on any platform (including mobile devices), should allow everyone to enjoy it. The source code is released under the GNU AGPL to ensure that the users' freedoms are preserved even if this game is rendered or in any way run server-side.
2012-03-11 21:38:06 -04:00
/**
* Handles the masking of tile sets
*
* Copyright (C) 2012 Mike Gerwitz
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
* This handles the masking and slicing of tiles found in LTG files (see
* LtgLoader for more information).
*
* We must think back to the good ol' days - before transparency was represented
* in the image format itself and before alphatransparency even existed. Bitmaps
* have no alpha channel like PNG, nor can they designate any color as
* transparent like GIFs. That is what the mask bitmap is for. In the case of
* the LT mask, black is used to denote opacity whereas white denotes
* transparency. Furthermore, not all tiles have masks associated with them
* (e.g. blocks and walls). Rather than those mask tiles being represented as
* solid black boxes, some tileset masks are solid *white*. This, as we will
* see, complicates our masking algorithm.
*
* When rendering the tiles, we obviously need to support transparency. In the
* browser, this must be done either with a GIF or an alpha channel. In other
* words --- we need to apply the mask to the tiles to result in a tile set with
* an alpha channel which can be directly drawn to the screen. Applying this
* mask when the LTG file is initially loaded will also improve performance by
* eliminating the need to re-apply the mask each and every time a particular
* tile is drawn.
*
* To apply this mask, since CSS masking is (at this time) in its infancy, we
* must use the canvas element. Canvas XOR masks, however, do not help us ---
* they apply the mask using the alpha channel, whereas we want to apply based
* on the brightness of a particular pixel. Therefore, our solution will be to
* grab the image data and manipulate each pixel individually, adjusting the
* alpha channel to either 255 for opaque or 0 for transparent. Since the mask
* is either black or white, we needn't calculate the brightness --- we can
* simply check the color value of a single channel (e.g. R) and make the pixel
* entirely transparent if the value is !== 0.
*
* Remember that certain tiles contain no mask. Since they are not filled with
* black, we must be able to recognize when we should *not* apply a mask;
* otherwise, the tile will be entire transparent! Coupling this unfortunate
* detail with the fact that putImageData() does not support slicing like
* drawImage() does, it makes more sense to store each tile individually in
* memory rather than as a single image. Otherwise, we would be forced to use
* drawImage() and re-apply the mask each time a tile is drawn, which is not
* worth the little extra memory that will be consumed by separate tile images.
* Given this implementation, we may then let the LtgLoader know specifically
* what tiles should have masks applied.
*
* With that, we should have an easy-to-use set of tile graphics ready for
* rendering.
*/
/**
* Slices tiles and applies masks
*/
ltjs.TileMasker = Class( 'TileMasker',
{
/**
* Canvas 2D context (used for masking and tile slicing)
* @type {CanvasRenderingContext2d}
*/
'private _context': null,
2012-03-12 21:14:39 -04:00
/**
* Tile definition to use for all operations
* @type {Array.<Array.<string,number>>}
*/
'private _tileDfn': null,
/**
* Width of each individual tile
* @type {number}
*/
'private _tileWidth': 0,
/**
* Height of each individual tile
* @type {number}
*/
'private _tileHeight': 0,
/**
* Number of tiles per row
* @type {number}
*/
'private _tilesPerRow': 0,
/**
* Calculated width of tile set provided a tile definition
* @type {number}
*/
'private _setWidth': 0,
/**
* Calculated height of tile set provided a tile definition
* @type {number}
*/
'private _setHeight': 0,
Initial commit illustrating LTG loading and masking This project -- creating a LaserTank clone in JavaScript -- deserves some discussion before diving into the implementation details (you may skip to the separator below for implementation details without the history). LaserTank is a game that has a special place in my heart. Not only is it a witty and enjoyable game, but it is in fact the reason I began programming as a young boy in the first place. While it is likely that I would have eventually picked up a programming book for some other reason, I owe that point in time entirely to this game. Allow me to explain. At the age of 10, I would spend much of my time on the Internet downloading various games and demos that may satisfy my interests (the old days of CNET's download.com showed me many of those, I believe). One of those games that immediately captivated me was LaserTank, but not purely for reasons of gameplay. It had this wonderful feature that added so much potential (and replayability) to the game -- a map editor. I found myself enthralled with the map editor to the point where I spent all my time creating maps rather than playing the game. What fascinated me was the ability to essentially create portions of the game -- to tell the game what to do and how to function. I was not able to create my own game using this editor, but I felt like I was creating portions of it. That said, I soon realized that it wasn't enough; I needed to do more. It was the limitations of the map editor and the enjoyment of creating the maps that caused me to convince my parents to take me to Barnes and Noble to purchase my first programming book - Learn to Program with Visual Basic 6 by John Smiley (N.B. Visual Basic is proprietary and I cannot recommend using it. I had no knowledge of the evils of proprietary software back at that point in time.) My parents had their doubts, but that only pushed me even harder to learn. Ironically, the book was about creating a business application. I found this process very enjoyable and began focusing more on conventional desktop software rather than gaming. I did create a couple games (bop-a-mole and breakout among others), but my focus was never game development. Eventually, I moved on from Visual Basic and got into web development. Following that, I discovered GNU/Linux and began getting more and more into lower-level systems and languages (such as C and ASM) and began adopting the hacker ethic. That brings us to where I am today -- nearly 13 years later. It would only seem fitting to bring my hobby-turned-career full circle by cloning the very game that started everything. When I say "clone", I mean nothing more; there will be no additional features or modifications to the original gameplay, menus, graphics, etc. It will support all original file formats (I will develop none of my own). The only differences between the clone and the original game will arise from the obvious issues introduced by cloning the game on a web platform. Specifically, the user will have the option to load files from either their local box or a remote resource, and I may provide pre-masked tile sets for browsers that do not support the canvas element (a fallback mode, if you will). No matter what the change, though, the gameplay will remain identical. That said, the library resulting from the clone will be built with extensibility in mind. If a user (or myself) wishes to create a derivative work by hooking or extending the library (for example, to support larger maps, add additional blocks/enemies, multiplayer support, etc), that should be fairly trivial to do. However, those works will be entirely separate from the clone and clearly distinguished. I think the original LaserTank is perfect the way it is. Remember, it has a special place in my heart (aww) and I would like to preserve the game as I remember it back then. --- Alright; now that we have a great deal of unnecessary history out of the way, let's get into the implementation details for this commit (if you're reading this as a blog entry, see the first commit). This commit represents a proof-of-concept showing that the LTG files (containing the LaserTank graphics) can be properly loaded and their masks properly applied. This was the first major concern for the project and, if a workaround were needed, would have prevented me from creating a full clone (as it would not support loading LTG files without having them first sent to the server, processed, and returned in a different format). The LTG file contains some metadata (including the name, author and description) as well as two bitmaps (BMPs) -- the game tileset and the associated mask. The game bitmap's position was static, but its length and the offset of the mask bitmap were provided by the four bytes immediately preceding the game bitmap (the TLTGREC struct in LTANK.H of the original sources represented this value as a DWORD, which represents a 32-bit integer). The only challenge with converting this value into an integer that we could use is its endianness -- is the most significant byte at the beginning or end? Windows programs (of which LaserTank is) generally write in little-endian format, but to be sure we can simply open up the LTG file in your favorite HEX editor (I simply use `xxd`). In the case of the original tileset, the four bytes immediately preceding the bitmap header (as identified by 'BM', or `424d`) were `7a f5 00 00`, which on its own clearly indicates little-endianness. We can verify by searching for 'BM' once again, and finding that it begins at location `f5 7a` (if your HEX editor displays in big-endian format). To convert into a number, we can simply add up each byte individually, left-shifting by 8N bits, where N is the 0-indexed byte position. Loading the BMP files was then fairly trivial; the file could be loaded into memory (read from disk using FileReader) and we could cut the relevant portions of the binary string out. The bitmaps could then be base64-encoded and the "src" attribute of an Image object set to 'data:image/bmp;base64,B', where B is the base64-encoded BMP. This could then be rendered however we please - CSS sprites or to a canvas. The problem with CSS sprites is that we need to apply the mask and there is no reliable way to do this without a canvas; transparency in the browser is normally handled using GIFs or PNGs. As it turns out, the canvas performs masking using the alpha channel as well, so I would have to create my own masking algorithm to manipulate the alpha channel of the tileset. To complicate matters even more, certain tiles had no mask, and they did not consistently represent the mask with all black (black is used in LT to indicate opacity), meaning that the algorithm would have to understand what tiles should be skipped entirely. The solution was to simply loop through each tile and set the alpha byte of each pixel relative to the respective pixel on the map. Because the images were created out of data in memory, the canvas is not tainted when the image is rendered before using getImageData(). To help speed up the process, since we know that the mask can only contain black and white, we need only check one of the channels; we do not need to calculate brightness. This process successfully returns each individual tile, properly masked, which can be rendered to the canvas using putImageData(). Crisis averted. With that major concern out of the way, the clone should no longer be a problem. I go into more detail in the comments within LtgLoader and TileMasker. This is going to be an exciting process, both because of the LT clone itself and because this is my first experience working with the canvas element. As aforementioned, I seldom create games and I have had no use for the canvas thus far. Hopefully this project will be well-received by both those who have played LaserTank in the past and the original developer of the game (Jim Kindley, JEK Software). LaserTank is a fairly old game and is not likely to be well known anymore, but the game itself is a blast (no pun intended) and bringing it to the browser, where it can be used on any platform (including mobile devices), should allow everyone to enjoy it. The source code is released under the GNU AGPL to ensure that the users' freedoms are preserved even if this game is rendered or in any way run server-side.
2012-03-11 21:38:06 -04:00
/**
2012-03-12 21:14:39 -04:00
* Initialize loader with a tile definition
*
* The tile definition defines how a tile set should be interpreted. This
* allows us to support *any* type of tile set -- not just those that are
* defined by the original game.
Initial commit illustrating LTG loading and masking This project -- creating a LaserTank clone in JavaScript -- deserves some discussion before diving into the implementation details (you may skip to the separator below for implementation details without the history). LaserTank is a game that has a special place in my heart. Not only is it a witty and enjoyable game, but it is in fact the reason I began programming as a young boy in the first place. While it is likely that I would have eventually picked up a programming book for some other reason, I owe that point in time entirely to this game. Allow me to explain. At the age of 10, I would spend much of my time on the Internet downloading various games and demos that may satisfy my interests (the old days of CNET's download.com showed me many of those, I believe). One of those games that immediately captivated me was LaserTank, but not purely for reasons of gameplay. It had this wonderful feature that added so much potential (and replayability) to the game -- a map editor. I found myself enthralled with the map editor to the point where I spent all my time creating maps rather than playing the game. What fascinated me was the ability to essentially create portions of the game -- to tell the game what to do and how to function. I was not able to create my own game using this editor, but I felt like I was creating portions of it. That said, I soon realized that it wasn't enough; I needed to do more. It was the limitations of the map editor and the enjoyment of creating the maps that caused me to convince my parents to take me to Barnes and Noble to purchase my first programming book - Learn to Program with Visual Basic 6 by John Smiley (N.B. Visual Basic is proprietary and I cannot recommend using it. I had no knowledge of the evils of proprietary software back at that point in time.) My parents had their doubts, but that only pushed me even harder to learn. Ironically, the book was about creating a business application. I found this process very enjoyable and began focusing more on conventional desktop software rather than gaming. I did create a couple games (bop-a-mole and breakout among others), but my focus was never game development. Eventually, I moved on from Visual Basic and got into web development. Following that, I discovered GNU/Linux and began getting more and more into lower-level systems and languages (such as C and ASM) and began adopting the hacker ethic. That brings us to where I am today -- nearly 13 years later. It would only seem fitting to bring my hobby-turned-career full circle by cloning the very game that started everything. When I say "clone", I mean nothing more; there will be no additional features or modifications to the original gameplay, menus, graphics, etc. It will support all original file formats (I will develop none of my own). The only differences between the clone and the original game will arise from the obvious issues introduced by cloning the game on a web platform. Specifically, the user will have the option to load files from either their local box or a remote resource, and I may provide pre-masked tile sets for browsers that do not support the canvas element (a fallback mode, if you will). No matter what the change, though, the gameplay will remain identical. That said, the library resulting from the clone will be built with extensibility in mind. If a user (or myself) wishes to create a derivative work by hooking or extending the library (for example, to support larger maps, add additional blocks/enemies, multiplayer support, etc), that should be fairly trivial to do. However, those works will be entirely separate from the clone and clearly distinguished. I think the original LaserTank is perfect the way it is. Remember, it has a special place in my heart (aww) and I would like to preserve the game as I remember it back then. --- Alright; now that we have a great deal of unnecessary history out of the way, let's get into the implementation details for this commit (if you're reading this as a blog entry, see the first commit). This commit represents a proof-of-concept showing that the LTG files (containing the LaserTank graphics) can be properly loaded and their masks properly applied. This was the first major concern for the project and, if a workaround were needed, would have prevented me from creating a full clone (as it would not support loading LTG files without having them first sent to the server, processed, and returned in a different format). The LTG file contains some metadata (including the name, author and description) as well as two bitmaps (BMPs) -- the game tileset and the associated mask. The game bitmap's position was static, but its length and the offset of the mask bitmap were provided by the four bytes immediately preceding the game bitmap (the TLTGREC struct in LTANK.H of the original sources represented this value as a DWORD, which represents a 32-bit integer). The only challenge with converting this value into an integer that we could use is its endianness -- is the most significant byte at the beginning or end? Windows programs (of which LaserTank is) generally write in little-endian format, but to be sure we can simply open up the LTG file in your favorite HEX editor (I simply use `xxd`). In the case of the original tileset, the four bytes immediately preceding the bitmap header (as identified by 'BM', or `424d`) were `7a f5 00 00`, which on its own clearly indicates little-endianness. We can verify by searching for 'BM' once again, and finding that it begins at location `f5 7a` (if your HEX editor displays in big-endian format). To convert into a number, we can simply add up each byte individually, left-shifting by 8N bits, where N is the 0-indexed byte position. Loading the BMP files was then fairly trivial; the file could be loaded into memory (read from disk using FileReader) and we could cut the relevant portions of the binary string out. The bitmaps could then be base64-encoded and the "src" attribute of an Image object set to 'data:image/bmp;base64,B', where B is the base64-encoded BMP. This could then be rendered however we please - CSS sprites or to a canvas. The problem with CSS sprites is that we need to apply the mask and there is no reliable way to do this without a canvas; transparency in the browser is normally handled using GIFs or PNGs. As it turns out, the canvas performs masking using the alpha channel as well, so I would have to create my own masking algorithm to manipulate the alpha channel of the tileset. To complicate matters even more, certain tiles had no mask, and they did not consistently represent the mask with all black (black is used in LT to indicate opacity), meaning that the algorithm would have to understand what tiles should be skipped entirely. The solution was to simply loop through each tile and set the alpha byte of each pixel relative to the respective pixel on the map. Because the images were created out of data in memory, the canvas is not tainted when the image is rendered before using getImageData(). To help speed up the process, since we know that the mask can only contain black and white, we need only check one of the channels; we do not need to calculate brightness. This process successfully returns each individual tile, properly masked, which can be rendered to the canvas using putImageData(). Crisis averted. With that major concern out of the way, the clone should no longer be a problem. I go into more detail in the comments within LtgLoader and TileMasker. This is going to be an exciting process, both because of the LT clone itself and because this is my first experience working with the canvas element. As aforementioned, I seldom create games and I have had no use for the canvas thus far. Hopefully this project will be well-received by both those who have played LaserTank in the past and the original developer of the game (Jim Kindley, JEK Software). LaserTank is a fairly old game and is not likely to be well known anymore, but the game itself is a blast (no pun intended) and bringing it to the browser, where it can be used on any platform (including mobile devices), should allow everyone to enjoy it. The source code is released under the GNU AGPL to ensure that the users' freedoms are preserved even if this game is rendered or in any way run server-side.
2012-03-11 21:38:06 -04:00
*
2012-03-12 21:14:39 -04:00
* @param {ltjs.TileDfn} tile_dfn tile definition object
Initial commit illustrating LTG loading and masking This project -- creating a LaserTank clone in JavaScript -- deserves some discussion before diving into the implementation details (you may skip to the separator below for implementation details without the history). LaserTank is a game that has a special place in my heart. Not only is it a witty and enjoyable game, but it is in fact the reason I began programming as a young boy in the first place. While it is likely that I would have eventually picked up a programming book for some other reason, I owe that point in time entirely to this game. Allow me to explain. At the age of 10, I would spend much of my time on the Internet downloading various games and demos that may satisfy my interests (the old days of CNET's download.com showed me many of those, I believe). One of those games that immediately captivated me was LaserTank, but not purely for reasons of gameplay. It had this wonderful feature that added so much potential (and replayability) to the game -- a map editor. I found myself enthralled with the map editor to the point where I spent all my time creating maps rather than playing the game. What fascinated me was the ability to essentially create portions of the game -- to tell the game what to do and how to function. I was not able to create my own game using this editor, but I felt like I was creating portions of it. That said, I soon realized that it wasn't enough; I needed to do more. It was the limitations of the map editor and the enjoyment of creating the maps that caused me to convince my parents to take me to Barnes and Noble to purchase my first programming book - Learn to Program with Visual Basic 6 by John Smiley (N.B. Visual Basic is proprietary and I cannot recommend using it. I had no knowledge of the evils of proprietary software back at that point in time.) My parents had their doubts, but that only pushed me even harder to learn. Ironically, the book was about creating a business application. I found this process very enjoyable and began focusing more on conventional desktop software rather than gaming. I did create a couple games (bop-a-mole and breakout among others), but my focus was never game development. Eventually, I moved on from Visual Basic and got into web development. Following that, I discovered GNU/Linux and began getting more and more into lower-level systems and languages (such as C and ASM) and began adopting the hacker ethic. That brings us to where I am today -- nearly 13 years later. It would only seem fitting to bring my hobby-turned-career full circle by cloning the very game that started everything. When I say "clone", I mean nothing more; there will be no additional features or modifications to the original gameplay, menus, graphics, etc. It will support all original file formats (I will develop none of my own). The only differences between the clone and the original game will arise from the obvious issues introduced by cloning the game on a web platform. Specifically, the user will have the option to load files from either their local box or a remote resource, and I may provide pre-masked tile sets for browsers that do not support the canvas element (a fallback mode, if you will). No matter what the change, though, the gameplay will remain identical. That said, the library resulting from the clone will be built with extensibility in mind. If a user (or myself) wishes to create a derivative work by hooking or extending the library (for example, to support larger maps, add additional blocks/enemies, multiplayer support, etc), that should be fairly trivial to do. However, those works will be entirely separate from the clone and clearly distinguished. I think the original LaserTank is perfect the way it is. Remember, it has a special place in my heart (aww) and I would like to preserve the game as I remember it back then. --- Alright; now that we have a great deal of unnecessary history out of the way, let's get into the implementation details for this commit (if you're reading this as a blog entry, see the first commit). This commit represents a proof-of-concept showing that the LTG files (containing the LaserTank graphics) can be properly loaded and their masks properly applied. This was the first major concern for the project and, if a workaround were needed, would have prevented me from creating a full clone (as it would not support loading LTG files without having them first sent to the server, processed, and returned in a different format). The LTG file contains some metadata (including the name, author and description) as well as two bitmaps (BMPs) -- the game tileset and the associated mask. The game bitmap's position was static, but its length and the offset of the mask bitmap were provided by the four bytes immediately preceding the game bitmap (the TLTGREC struct in LTANK.H of the original sources represented this value as a DWORD, which represents a 32-bit integer). The only challenge with converting this value into an integer that we could use is its endianness -- is the most significant byte at the beginning or end? Windows programs (of which LaserTank is) generally write in little-endian format, but to be sure we can simply open up the LTG file in your favorite HEX editor (I simply use `xxd`). In the case of the original tileset, the four bytes immediately preceding the bitmap header (as identified by 'BM', or `424d`) were `7a f5 00 00`, which on its own clearly indicates little-endianness. We can verify by searching for 'BM' once again, and finding that it begins at location `f5 7a` (if your HEX editor displays in big-endian format). To convert into a number, we can simply add up each byte individually, left-shifting by 8N bits, where N is the 0-indexed byte position. Loading the BMP files was then fairly trivial; the file could be loaded into memory (read from disk using FileReader) and we could cut the relevant portions of the binary string out. The bitmaps could then be base64-encoded and the "src" attribute of an Image object set to 'data:image/bmp;base64,B', where B is the base64-encoded BMP. This could then be rendered however we please - CSS sprites or to a canvas. The problem with CSS sprites is that we need to apply the mask and there is no reliable way to do this without a canvas; transparency in the browser is normally handled using GIFs or PNGs. As it turns out, the canvas performs masking using the alpha channel as well, so I would have to create my own masking algorithm to manipulate the alpha channel of the tileset. To complicate matters even more, certain tiles had no mask, and they did not consistently represent the mask with all black (black is used in LT to indicate opacity), meaning that the algorithm would have to understand what tiles should be skipped entirely. The solution was to simply loop through each tile and set the alpha byte of each pixel relative to the respective pixel on the map. Because the images were created out of data in memory, the canvas is not tainted when the image is rendered before using getImageData(). To help speed up the process, since we know that the mask can only contain black and white, we need only check one of the channels; we do not need to calculate brightness. This process successfully returns each individual tile, properly masked, which can be rendered to the canvas using putImageData(). Crisis averted. With that major concern out of the way, the clone should no longer be a problem. I go into more detail in the comments within LtgLoader and TileMasker. This is going to be an exciting process, both because of the LT clone itself and because this is my first experience working with the canvas element. As aforementioned, I seldom create games and I have had no use for the canvas thus far. Hopefully this project will be well-received by both those who have played LaserTank in the past and the original developer of the game (Jim Kindley, JEK Software). LaserTank is a fairly old game and is not likely to be well known anymore, but the game itself is a blast (no pun intended) and bringing it to the browser, where it can be used on any platform (including mobile devices), should allow everyone to enjoy it. The source code is released under the GNU AGPL to ensure that the users' freedoms are preserved even if this game is rendered or in any way run server-side.
2012-03-11 21:38:06 -04:00
*/
2012-03-12 21:14:39 -04:00
__construct: function( tile_dfn )
Initial commit illustrating LTG loading and masking This project -- creating a LaserTank clone in JavaScript -- deserves some discussion before diving into the implementation details (you may skip to the separator below for implementation details without the history). LaserTank is a game that has a special place in my heart. Not only is it a witty and enjoyable game, but it is in fact the reason I began programming as a young boy in the first place. While it is likely that I would have eventually picked up a programming book for some other reason, I owe that point in time entirely to this game. Allow me to explain. At the age of 10, I would spend much of my time on the Internet downloading various games and demos that may satisfy my interests (the old days of CNET's download.com showed me many of those, I believe). One of those games that immediately captivated me was LaserTank, but not purely for reasons of gameplay. It had this wonderful feature that added so much potential (and replayability) to the game -- a map editor. I found myself enthralled with the map editor to the point where I spent all my time creating maps rather than playing the game. What fascinated me was the ability to essentially create portions of the game -- to tell the game what to do and how to function. I was not able to create my own game using this editor, but I felt like I was creating portions of it. That said, I soon realized that it wasn't enough; I needed to do more. It was the limitations of the map editor and the enjoyment of creating the maps that caused me to convince my parents to take me to Barnes and Noble to purchase my first programming book - Learn to Program with Visual Basic 6 by John Smiley (N.B. Visual Basic is proprietary and I cannot recommend using it. I had no knowledge of the evils of proprietary software back at that point in time.) My parents had their doubts, but that only pushed me even harder to learn. Ironically, the book was about creating a business application. I found this process very enjoyable and began focusing more on conventional desktop software rather than gaming. I did create a couple games (bop-a-mole and breakout among others), but my focus was never game development. Eventually, I moved on from Visual Basic and got into web development. Following that, I discovered GNU/Linux and began getting more and more into lower-level systems and languages (such as C and ASM) and began adopting the hacker ethic. That brings us to where I am today -- nearly 13 years later. It would only seem fitting to bring my hobby-turned-career full circle by cloning the very game that started everything. When I say "clone", I mean nothing more; there will be no additional features or modifications to the original gameplay, menus, graphics, etc. It will support all original file formats (I will develop none of my own). The only differences between the clone and the original game will arise from the obvious issues introduced by cloning the game on a web platform. Specifically, the user will have the option to load files from either their local box or a remote resource, and I may provide pre-masked tile sets for browsers that do not support the canvas element (a fallback mode, if you will). No matter what the change, though, the gameplay will remain identical. That said, the library resulting from the clone will be built with extensibility in mind. If a user (or myself) wishes to create a derivative work by hooking or extending the library (for example, to support larger maps, add additional blocks/enemies, multiplayer support, etc), that should be fairly trivial to do. However, those works will be entirely separate from the clone and clearly distinguished. I think the original LaserTank is perfect the way it is. Remember, it has a special place in my heart (aww) and I would like to preserve the game as I remember it back then. --- Alright; now that we have a great deal of unnecessary history out of the way, let's get into the implementation details for this commit (if you're reading this as a blog entry, see the first commit). This commit represents a proof-of-concept showing that the LTG files (containing the LaserTank graphics) can be properly loaded and their masks properly applied. This was the first major concern for the project and, if a workaround were needed, would have prevented me from creating a full clone (as it would not support loading LTG files without having them first sent to the server, processed, and returned in a different format). The LTG file contains some metadata (including the name, author and description) as well as two bitmaps (BMPs) -- the game tileset and the associated mask. The game bitmap's position was static, but its length and the offset of the mask bitmap were provided by the four bytes immediately preceding the game bitmap (the TLTGREC struct in LTANK.H of the original sources represented this value as a DWORD, which represents a 32-bit integer). The only challenge with converting this value into an integer that we could use is its endianness -- is the most significant byte at the beginning or end? Windows programs (of which LaserTank is) generally write in little-endian format, but to be sure we can simply open up the LTG file in your favorite HEX editor (I simply use `xxd`). In the case of the original tileset, the four bytes immediately preceding the bitmap header (as identified by 'BM', or `424d`) were `7a f5 00 00`, which on its own clearly indicates little-endianness. We can verify by searching for 'BM' once again, and finding that it begins at location `f5 7a` (if your HEX editor displays in big-endian format). To convert into a number, we can simply add up each byte individually, left-shifting by 8N bits, where N is the 0-indexed byte position. Loading the BMP files was then fairly trivial; the file could be loaded into memory (read from disk using FileReader) and we could cut the relevant portions of the binary string out. The bitmaps could then be base64-encoded and the "src" attribute of an Image object set to 'data:image/bmp;base64,B', where B is the base64-encoded BMP. This could then be rendered however we please - CSS sprites or to a canvas. The problem with CSS sprites is that we need to apply the mask and there is no reliable way to do this without a canvas; transparency in the browser is normally handled using GIFs or PNGs. As it turns out, the canvas performs masking using the alpha channel as well, so I would have to create my own masking algorithm to manipulate the alpha channel of the tileset. To complicate matters even more, certain tiles had no mask, and they did not consistently represent the mask with all black (black is used in LT to indicate opacity), meaning that the algorithm would have to understand what tiles should be skipped entirely. The solution was to simply loop through each tile and set the alpha byte of each pixel relative to the respective pixel on the map. Because the images were created out of data in memory, the canvas is not tainted when the image is rendered before using getImageData(). To help speed up the process, since we know that the mask can only contain black and white, we need only check one of the channels; we do not need to calculate brightness. This process successfully returns each individual tile, properly masked, which can be rendered to the canvas using putImageData(). Crisis averted. With that major concern out of the way, the clone should no longer be a problem. I go into more detail in the comments within LtgLoader and TileMasker. This is going to be an exciting process, both because of the LT clone itself and because this is my first experience working with the canvas element. As aforementioned, I seldom create games and I have had no use for the canvas thus far. Hopefully this project will be well-received by both those who have played LaserTank in the past and the original developer of the game (Jim Kindley, JEK Software). LaserTank is a fairly old game and is not likely to be well known anymore, but the game itself is a blast (no pun intended) and bringing it to the browser, where it can be used on any platform (including mobile devices), should allow everyone to enjoy it. The source code is released under the GNU AGPL to ensure that the users' freedoms are preserved even if this game is rendered or in any way run server-side.
2012-03-11 21:38:06 -04:00
{
2012-03-12 21:14:39 -04:00
if ( !( Class.isA( ltjs.TileDfn, tile_dfn ) ) )
{
throw TypeError( "Invalid tile definition provided." );
}
// pre-calculate our tile information
this._tileDfn = tile_dfn.getTileDefinition();
this._calcSetDimensions( tile_dfn );
Initial commit illustrating LTG loading and masking This project -- creating a LaserTank clone in JavaScript -- deserves some discussion before diving into the implementation details (you may skip to the separator below for implementation details without the history). LaserTank is a game that has a special place in my heart. Not only is it a witty and enjoyable game, but it is in fact the reason I began programming as a young boy in the first place. While it is likely that I would have eventually picked up a programming book for some other reason, I owe that point in time entirely to this game. Allow me to explain. At the age of 10, I would spend much of my time on the Internet downloading various games and demos that may satisfy my interests (the old days of CNET's download.com showed me many of those, I believe). One of those games that immediately captivated me was LaserTank, but not purely for reasons of gameplay. It had this wonderful feature that added so much potential (and replayability) to the game -- a map editor. I found myself enthralled with the map editor to the point where I spent all my time creating maps rather than playing the game. What fascinated me was the ability to essentially create portions of the game -- to tell the game what to do and how to function. I was not able to create my own game using this editor, but I felt like I was creating portions of it. That said, I soon realized that it wasn't enough; I needed to do more. It was the limitations of the map editor and the enjoyment of creating the maps that caused me to convince my parents to take me to Barnes and Noble to purchase my first programming book - Learn to Program with Visual Basic 6 by John Smiley (N.B. Visual Basic is proprietary and I cannot recommend using it. I had no knowledge of the evils of proprietary software back at that point in time.) My parents had their doubts, but that only pushed me even harder to learn. Ironically, the book was about creating a business application. I found this process very enjoyable and began focusing more on conventional desktop software rather than gaming. I did create a couple games (bop-a-mole and breakout among others), but my focus was never game development. Eventually, I moved on from Visual Basic and got into web development. Following that, I discovered GNU/Linux and began getting more and more into lower-level systems and languages (such as C and ASM) and began adopting the hacker ethic. That brings us to where I am today -- nearly 13 years later. It would only seem fitting to bring my hobby-turned-career full circle by cloning the very game that started everything. When I say "clone", I mean nothing more; there will be no additional features or modifications to the original gameplay, menus, graphics, etc. It will support all original file formats (I will develop none of my own). The only differences between the clone and the original game will arise from the obvious issues introduced by cloning the game on a web platform. Specifically, the user will have the option to load files from either their local box or a remote resource, and I may provide pre-masked tile sets for browsers that do not support the canvas element (a fallback mode, if you will). No matter what the change, though, the gameplay will remain identical. That said, the library resulting from the clone will be built with extensibility in mind. If a user (or myself) wishes to create a derivative work by hooking or extending the library (for example, to support larger maps, add additional blocks/enemies, multiplayer support, etc), that should be fairly trivial to do. However, those works will be entirely separate from the clone and clearly distinguished. I think the original LaserTank is perfect the way it is. Remember, it has a special place in my heart (aww) and I would like to preserve the game as I remember it back then. --- Alright; now that we have a great deal of unnecessary history out of the way, let's get into the implementation details for this commit (if you're reading this as a blog entry, see the first commit). This commit represents a proof-of-concept showing that the LTG files (containing the LaserTank graphics) can be properly loaded and their masks properly applied. This was the first major concern for the project and, if a workaround were needed, would have prevented me from creating a full clone (as it would not support loading LTG files without having them first sent to the server, processed, and returned in a different format). The LTG file contains some metadata (including the name, author and description) as well as two bitmaps (BMPs) -- the game tileset and the associated mask. The game bitmap's position was static, but its length and the offset of the mask bitmap were provided by the four bytes immediately preceding the game bitmap (the TLTGREC struct in LTANK.H of the original sources represented this value as a DWORD, which represents a 32-bit integer). The only challenge with converting this value into an integer that we could use is its endianness -- is the most significant byte at the beginning or end? Windows programs (of which LaserTank is) generally write in little-endian format, but to be sure we can simply open up the LTG file in your favorite HEX editor (I simply use `xxd`). In the case of the original tileset, the four bytes immediately preceding the bitmap header (as identified by 'BM', or `424d`) were `7a f5 00 00`, which on its own clearly indicates little-endianness. We can verify by searching for 'BM' once again, and finding that it begins at location `f5 7a` (if your HEX editor displays in big-endian format). To convert into a number, we can simply add up each byte individually, left-shifting by 8N bits, where N is the 0-indexed byte position. Loading the BMP files was then fairly trivial; the file could be loaded into memory (read from disk using FileReader) and we could cut the relevant portions of the binary string out. The bitmaps could then be base64-encoded and the "src" attribute of an Image object set to 'data:image/bmp;base64,B', where B is the base64-encoded BMP. This could then be rendered however we please - CSS sprites or to a canvas. The problem with CSS sprites is that we need to apply the mask and there is no reliable way to do this without a canvas; transparency in the browser is normally handled using GIFs or PNGs. As it turns out, the canvas performs masking using the alpha channel as well, so I would have to create my own masking algorithm to manipulate the alpha channel of the tileset. To complicate matters even more, certain tiles had no mask, and they did not consistently represent the mask with all black (black is used in LT to indicate opacity), meaning that the algorithm would have to understand what tiles should be skipped entirely. The solution was to simply loop through each tile and set the alpha byte of each pixel relative to the respective pixel on the map. Because the images were created out of data in memory, the canvas is not tainted when the image is rendered before using getImageData(). To help speed up the process, since we know that the mask can only contain black and white, we need only check one of the channels; we do not need to calculate brightness. This process successfully returns each individual tile, properly masked, which can be rendered to the canvas using putImageData(). Crisis averted. With that major concern out of the way, the clone should no longer be a problem. I go into more detail in the comments within LtgLoader and TileMasker. This is going to be an exciting process, both because of the LT clone itself and because this is my first experience working with the canvas element. As aforementioned, I seldom create games and I have had no use for the canvas thus far. Hopefully this project will be well-received by both those who have played LaserTank in the past and the original developer of the game (Jim Kindley, JEK Software). LaserTank is a fairly old game and is not likely to be well known anymore, but the game itself is a blast (no pun intended) and bringing it to the browser, where it can be used on any platform (including mobile devices), should allow everyone to enjoy it. The source code is released under the GNU AGPL to ensure that the users' freedoms are preserved even if this game is rendered or in any way run server-side.
2012-03-11 21:38:06 -04:00
// rather than accepting a context, we will create our own canvas in
// memory to perform our operations (it will not be added to the DOM, so
// these operations will not be visible to the user)
2012-03-12 21:14:39 -04:00
var context = document.createElement( 'canvas' ).getContext( '2d' );
Initial commit illustrating LTG loading and masking This project -- creating a LaserTank clone in JavaScript -- deserves some discussion before diving into the implementation details (you may skip to the separator below for implementation details without the history). LaserTank is a game that has a special place in my heart. Not only is it a witty and enjoyable game, but it is in fact the reason I began programming as a young boy in the first place. While it is likely that I would have eventually picked up a programming book for some other reason, I owe that point in time entirely to this game. Allow me to explain. At the age of 10, I would spend much of my time on the Internet downloading various games and demos that may satisfy my interests (the old days of CNET's download.com showed me many of those, I believe). One of those games that immediately captivated me was LaserTank, but not purely for reasons of gameplay. It had this wonderful feature that added so much potential (and replayability) to the game -- a map editor. I found myself enthralled with the map editor to the point where I spent all my time creating maps rather than playing the game. What fascinated me was the ability to essentially create portions of the game -- to tell the game what to do and how to function. I was not able to create my own game using this editor, but I felt like I was creating portions of it. That said, I soon realized that it wasn't enough; I needed to do more. It was the limitations of the map editor and the enjoyment of creating the maps that caused me to convince my parents to take me to Barnes and Noble to purchase my first programming book - Learn to Program with Visual Basic 6 by John Smiley (N.B. Visual Basic is proprietary and I cannot recommend using it. I had no knowledge of the evils of proprietary software back at that point in time.) My parents had their doubts, but that only pushed me even harder to learn. Ironically, the book was about creating a business application. I found this process very enjoyable and began focusing more on conventional desktop software rather than gaming. I did create a couple games (bop-a-mole and breakout among others), but my focus was never game development. Eventually, I moved on from Visual Basic and got into web development. Following that, I discovered GNU/Linux and began getting more and more into lower-level systems and languages (such as C and ASM) and began adopting the hacker ethic. That brings us to where I am today -- nearly 13 years later. It would only seem fitting to bring my hobby-turned-career full circle by cloning the very game that started everything. When I say "clone", I mean nothing more; there will be no additional features or modifications to the original gameplay, menus, graphics, etc. It will support all original file formats (I will develop none of my own). The only differences between the clone and the original game will arise from the obvious issues introduced by cloning the game on a web platform. Specifically, the user will have the option to load files from either their local box or a remote resource, and I may provide pre-masked tile sets for browsers that do not support the canvas element (a fallback mode, if you will). No matter what the change, though, the gameplay will remain identical. That said, the library resulting from the clone will be built with extensibility in mind. If a user (or myself) wishes to create a derivative work by hooking or extending the library (for example, to support larger maps, add additional blocks/enemies, multiplayer support, etc), that should be fairly trivial to do. However, those works will be entirely separate from the clone and clearly distinguished. I think the original LaserTank is perfect the way it is. Remember, it has a special place in my heart (aww) and I would like to preserve the game as I remember it back then. --- Alright; now that we have a great deal of unnecessary history out of the way, let's get into the implementation details for this commit (if you're reading this as a blog entry, see the first commit). This commit represents a proof-of-concept showing that the LTG files (containing the LaserTank graphics) can be properly loaded and their masks properly applied. This was the first major concern for the project and, if a workaround were needed, would have prevented me from creating a full clone (as it would not support loading LTG files without having them first sent to the server, processed, and returned in a different format). The LTG file contains some metadata (including the name, author and description) as well as two bitmaps (BMPs) -- the game tileset and the associated mask. The game bitmap's position was static, but its length and the offset of the mask bitmap were provided by the four bytes immediately preceding the game bitmap (the TLTGREC struct in LTANK.H of the original sources represented this value as a DWORD, which represents a 32-bit integer). The only challenge with converting this value into an integer that we could use is its endianness -- is the most significant byte at the beginning or end? Windows programs (of which LaserTank is) generally write in little-endian format, but to be sure we can simply open up the LTG file in your favorite HEX editor (I simply use `xxd`). In the case of the original tileset, the four bytes immediately preceding the bitmap header (as identified by 'BM', or `424d`) were `7a f5 00 00`, which on its own clearly indicates little-endianness. We can verify by searching for 'BM' once again, and finding that it begins at location `f5 7a` (if your HEX editor displays in big-endian format). To convert into a number, we can simply add up each byte individually, left-shifting by 8N bits, where N is the 0-indexed byte position. Loading the BMP files was then fairly trivial; the file could be loaded into memory (read from disk using FileReader) and we could cut the relevant portions of the binary string out. The bitmaps could then be base64-encoded and the "src" attribute of an Image object set to 'data:image/bmp;base64,B', where B is the base64-encoded BMP. This could then be rendered however we please - CSS sprites or to a canvas. The problem with CSS sprites is that we need to apply the mask and there is no reliable way to do this without a canvas; transparency in the browser is normally handled using GIFs or PNGs. As it turns out, the canvas performs masking using the alpha channel as well, so I would have to create my own masking algorithm to manipulate the alpha channel of the tileset. To complicate matters even more, certain tiles had no mask, and they did not consistently represent the mask with all black (black is used in LT to indicate opacity), meaning that the algorithm would have to understand what tiles should be skipped entirely. The solution was to simply loop through each tile and set the alpha byte of each pixel relative to the respective pixel on the map. Because the images were created out of data in memory, the canvas is not tainted when the image is rendered before using getImageData(). To help speed up the process, since we know that the mask can only contain black and white, we need only check one of the channels; we do not need to calculate brightness. This process successfully returns each individual tile, properly masked, which can be rendered to the canvas using putImageData(). Crisis averted. With that major concern out of the way, the clone should no longer be a problem. I go into more detail in the comments within LtgLoader and TileMasker. This is going to be an exciting process, both because of the LT clone itself and because this is my first experience working with the canvas element. As aforementioned, I seldom create games and I have had no use for the canvas thus far. Hopefully this project will be well-received by both those who have played LaserTank in the past and the original developer of the game (Jim Kindley, JEK Software). LaserTank is a fairly old game and is not likely to be well known anymore, but the game itself is a blast (no pun intended) and bringing it to the browser, where it can be used on any platform (including mobile devices), should allow everyone to enjoy it. The source code is released under the GNU AGPL to ensure that the users' freedoms are preserved even if this game is rendered or in any way run server-side.
2012-03-11 21:38:06 -04:00
// size the canvas so that it can fit the entire tileset
2012-03-12 21:14:39 -04:00
context.canvas.width = this._setWidth;
context.canvas.height = this._setHeight;
Initial commit illustrating LTG loading and masking This project -- creating a LaserTank clone in JavaScript -- deserves some discussion before diving into the implementation details (you may skip to the separator below for implementation details without the history). LaserTank is a game that has a special place in my heart. Not only is it a witty and enjoyable game, but it is in fact the reason I began programming as a young boy in the first place. While it is likely that I would have eventually picked up a programming book for some other reason, I owe that point in time entirely to this game. Allow me to explain. At the age of 10, I would spend much of my time on the Internet downloading various games and demos that may satisfy my interests (the old days of CNET's download.com showed me many of those, I believe). One of those games that immediately captivated me was LaserTank, but not purely for reasons of gameplay. It had this wonderful feature that added so much potential (and replayability) to the game -- a map editor. I found myself enthralled with the map editor to the point where I spent all my time creating maps rather than playing the game. What fascinated me was the ability to essentially create portions of the game -- to tell the game what to do and how to function. I was not able to create my own game using this editor, but I felt like I was creating portions of it. That said, I soon realized that it wasn't enough; I needed to do more. It was the limitations of the map editor and the enjoyment of creating the maps that caused me to convince my parents to take me to Barnes and Noble to purchase my first programming book - Learn to Program with Visual Basic 6 by John Smiley (N.B. Visual Basic is proprietary and I cannot recommend using it. I had no knowledge of the evils of proprietary software back at that point in time.) My parents had their doubts, but that only pushed me even harder to learn. Ironically, the book was about creating a business application. I found this process very enjoyable and began focusing more on conventional desktop software rather than gaming. I did create a couple games (bop-a-mole and breakout among others), but my focus was never game development. Eventually, I moved on from Visual Basic and got into web development. Following that, I discovered GNU/Linux and began getting more and more into lower-level systems and languages (such as C and ASM) and began adopting the hacker ethic. That brings us to where I am today -- nearly 13 years later. It would only seem fitting to bring my hobby-turned-career full circle by cloning the very game that started everything. When I say "clone", I mean nothing more; there will be no additional features or modifications to the original gameplay, menus, graphics, etc. It will support all original file formats (I will develop none of my own). The only differences between the clone and the original game will arise from the obvious issues introduced by cloning the game on a web platform. Specifically, the user will have the option to load files from either their local box or a remote resource, and I may provide pre-masked tile sets for browsers that do not support the canvas element (a fallback mode, if you will). No matter what the change, though, the gameplay will remain identical. That said, the library resulting from the clone will be built with extensibility in mind. If a user (or myself) wishes to create a derivative work by hooking or extending the library (for example, to support larger maps, add additional blocks/enemies, multiplayer support, etc), that should be fairly trivial to do. However, those works will be entirely separate from the clone and clearly distinguished. I think the original LaserTank is perfect the way it is. Remember, it has a special place in my heart (aww) and I would like to preserve the game as I remember it back then. --- Alright; now that we have a great deal of unnecessary history out of the way, let's get into the implementation details for this commit (if you're reading this as a blog entry, see the first commit). This commit represents a proof-of-concept showing that the LTG files (containing the LaserTank graphics) can be properly loaded and their masks properly applied. This was the first major concern for the project and, if a workaround were needed, would have prevented me from creating a full clone (as it would not support loading LTG files without having them first sent to the server, processed, and returned in a different format). The LTG file contains some metadata (including the name, author and description) as well as two bitmaps (BMPs) -- the game tileset and the associated mask. The game bitmap's position was static, but its length and the offset of the mask bitmap were provided by the four bytes immediately preceding the game bitmap (the TLTGREC struct in LTANK.H of the original sources represented this value as a DWORD, which represents a 32-bit integer). The only challenge with converting this value into an integer that we could use is its endianness -- is the most significant byte at the beginning or end? Windows programs (of which LaserTank is) generally write in little-endian format, but to be sure we can simply open up the LTG file in your favorite HEX editor (I simply use `xxd`). In the case of the original tileset, the four bytes immediately preceding the bitmap header (as identified by 'BM', or `424d`) were `7a f5 00 00`, which on its own clearly indicates little-endianness. We can verify by searching for 'BM' once again, and finding that it begins at location `f5 7a` (if your HEX editor displays in big-endian format). To convert into a number, we can simply add up each byte individually, left-shifting by 8N bits, where N is the 0-indexed byte position. Loading the BMP files was then fairly trivial; the file could be loaded into memory (read from disk using FileReader) and we could cut the relevant portions of the binary string out. The bitmaps could then be base64-encoded and the "src" attribute of an Image object set to 'data:image/bmp;base64,B', where B is the base64-encoded BMP. This could then be rendered however we please - CSS sprites or to a canvas. The problem with CSS sprites is that we need to apply the mask and there is no reliable way to do this without a canvas; transparency in the browser is normally handled using GIFs or PNGs. As it turns out, the canvas performs masking using the alpha channel as well, so I would have to create my own masking algorithm to manipulate the alpha channel of the tileset. To complicate matters even more, certain tiles had no mask, and they did not consistently represent the mask with all black (black is used in LT to indicate opacity), meaning that the algorithm would have to understand what tiles should be skipped entirely. The solution was to simply loop through each tile and set the alpha byte of each pixel relative to the respective pixel on the map. Because the images were created out of data in memory, the canvas is not tainted when the image is rendered before using getImageData(). To help speed up the process, since we know that the mask can only contain black and white, we need only check one of the channels; we do not need to calculate brightness. This process successfully returns each individual tile, properly masked, which can be rendered to the canvas using putImageData(). Crisis averted. With that major concern out of the way, the clone should no longer be a problem. I go into more detail in the comments within LtgLoader and TileMasker. This is going to be an exciting process, both because of the LT clone itself and because this is my first experience working with the canvas element. As aforementioned, I seldom create games and I have had no use for the canvas thus far. Hopefully this project will be well-received by both those who have played LaserTank in the past and the original developer of the game (Jim Kindley, JEK Software). LaserTank is a fairly old game and is not likely to be well known anymore, but the game itself is a blast (no pun intended) and bringing it to the browser, where it can be used on any platform (including mobile devices), should allow everyone to enjoy it. The source code is released under the GNU AGPL to ensure that the users' freedoms are preserved even if this game is rendered or in any way run server-side.
2012-03-11 21:38:06 -04:00
this._context = context;
},
2012-03-12 21:14:39 -04:00
/**
* Calculate tile set dimensions from the given tile definition object
*
* These dimensions are cached, as these are frequently used and it is
* unwise to continuously invoke methods unnecessarily (who knows what the
* developer of the given tile definition did!).
*
* @param {ltjs.TileDfn} tile_dfn tile definition object
*
* @return {undefined}
*/
'private _calcSetDimensions': function( tile_dfn )
{
// these vars are for clarity
var sizes = tile_dfn.getTileDimensions(),
n = this._tileDfn.length;
// store values so that we do not have to make additional calls to our
// TileDfn instance
this._tileWidth = sizes[ 0 ];
this._tileHeight = sizes[ 1 ];
this._tilesPerRow = sizes[ 2 ];
// calculate full width and height of tile set
this._setWidth = ( this._tileWidth * this._tilesPerRow );
this._setHeight = (
Math.ceil( n / this._tilesPerRow ) * this._tileHeight
);
},
Initial commit illustrating LTG loading and masking This project -- creating a LaserTank clone in JavaScript -- deserves some discussion before diving into the implementation details (you may skip to the separator below for implementation details without the history). LaserTank is a game that has a special place in my heart. Not only is it a witty and enjoyable game, but it is in fact the reason I began programming as a young boy in the first place. While it is likely that I would have eventually picked up a programming book for some other reason, I owe that point in time entirely to this game. Allow me to explain. At the age of 10, I would spend much of my time on the Internet downloading various games and demos that may satisfy my interests (the old days of CNET's download.com showed me many of those, I believe). One of those games that immediately captivated me was LaserTank, but not purely for reasons of gameplay. It had this wonderful feature that added so much potential (and replayability) to the game -- a map editor. I found myself enthralled with the map editor to the point where I spent all my time creating maps rather than playing the game. What fascinated me was the ability to essentially create portions of the game -- to tell the game what to do and how to function. I was not able to create my own game using this editor, but I felt like I was creating portions of it. That said, I soon realized that it wasn't enough; I needed to do more. It was the limitations of the map editor and the enjoyment of creating the maps that caused me to convince my parents to take me to Barnes and Noble to purchase my first programming book - Learn to Program with Visual Basic 6 by John Smiley (N.B. Visual Basic is proprietary and I cannot recommend using it. I had no knowledge of the evils of proprietary software back at that point in time.) My parents had their doubts, but that only pushed me even harder to learn. Ironically, the book was about creating a business application. I found this process very enjoyable and began focusing more on conventional desktop software rather than gaming. I did create a couple games (bop-a-mole and breakout among others), but my focus was never game development. Eventually, I moved on from Visual Basic and got into web development. Following that, I discovered GNU/Linux and began getting more and more into lower-level systems and languages (such as C and ASM) and began adopting the hacker ethic. That brings us to where I am today -- nearly 13 years later. It would only seem fitting to bring my hobby-turned-career full circle by cloning the very game that started everything. When I say "clone", I mean nothing more; there will be no additional features or modifications to the original gameplay, menus, graphics, etc. It will support all original file formats (I will develop none of my own). The only differences between the clone and the original game will arise from the obvious issues introduced by cloning the game on a web platform. Specifically, the user will have the option to load files from either their local box or a remote resource, and I may provide pre-masked tile sets for browsers that do not support the canvas element (a fallback mode, if you will). No matter what the change, though, the gameplay will remain identical. That said, the library resulting from the clone will be built with extensibility in mind. If a user (or myself) wishes to create a derivative work by hooking or extending the library (for example, to support larger maps, add additional blocks/enemies, multiplayer support, etc), that should be fairly trivial to do. However, those works will be entirely separate from the clone and clearly distinguished. I think the original LaserTank is perfect the way it is. Remember, it has a special place in my heart (aww) and I would like to preserve the game as I remember it back then. --- Alright; now that we have a great deal of unnecessary history out of the way, let's get into the implementation details for this commit (if you're reading this as a blog entry, see the first commit). This commit represents a proof-of-concept showing that the LTG files (containing the LaserTank graphics) can be properly loaded and their masks properly applied. This was the first major concern for the project and, if a workaround were needed, would have prevented me from creating a full clone (as it would not support loading LTG files without having them first sent to the server, processed, and returned in a different format). The LTG file contains some metadata (including the name, author and description) as well as two bitmaps (BMPs) -- the game tileset and the associated mask. The game bitmap's position was static, but its length and the offset of the mask bitmap were provided by the four bytes immediately preceding the game bitmap (the TLTGREC struct in LTANK.H of the original sources represented this value as a DWORD, which represents a 32-bit integer). The only challenge with converting this value into an integer that we could use is its endianness -- is the most significant byte at the beginning or end? Windows programs (of which LaserTank is) generally write in little-endian format, but to be sure we can simply open up the LTG file in your favorite HEX editor (I simply use `xxd`). In the case of the original tileset, the four bytes immediately preceding the bitmap header (as identified by 'BM', or `424d`) were `7a f5 00 00`, which on its own clearly indicates little-endianness. We can verify by searching for 'BM' once again, and finding that it begins at location `f5 7a` (if your HEX editor displays in big-endian format). To convert into a number, we can simply add up each byte individually, left-shifting by 8N bits, where N is the 0-indexed byte position. Loading the BMP files was then fairly trivial; the file could be loaded into memory (read from disk using FileReader) and we could cut the relevant portions of the binary string out. The bitmaps could then be base64-encoded and the "src" attribute of an Image object set to 'data:image/bmp;base64,B', where B is the base64-encoded BMP. This could then be rendered however we please - CSS sprites or to a canvas. The problem with CSS sprites is that we need to apply the mask and there is no reliable way to do this without a canvas; transparency in the browser is normally handled using GIFs or PNGs. As it turns out, the canvas performs masking using the alpha channel as well, so I would have to create my own masking algorithm to manipulate the alpha channel of the tileset. To complicate matters even more, certain tiles had no mask, and they did not consistently represent the mask with all black (black is used in LT to indicate opacity), meaning that the algorithm would have to understand what tiles should be skipped entirely. The solution was to simply loop through each tile and set the alpha byte of each pixel relative to the respective pixel on the map. Because the images were created out of data in memory, the canvas is not tainted when the image is rendered before using getImageData(). To help speed up the process, since we know that the mask can only contain black and white, we need only check one of the channels; we do not need to calculate brightness. This process successfully returns each individual tile, properly masked, which can be rendered to the canvas using putImageData(). Crisis averted. With that major concern out of the way, the clone should no longer be a problem. I go into more detail in the comments within LtgLoader and TileMasker. This is going to be an exciting process, both because of the LT clone itself and because this is my first experience working with the canvas element. As aforementioned, I seldom create games and I have had no use for the canvas thus far. Hopefully this project will be well-received by both those who have played LaserTank in the past and the original developer of the game (Jim Kindley, JEK Software). LaserTank is a fairly old game and is not likely to be well known anymore, but the game itself is a blast (no pun intended) and bringing it to the browser, where it can be used on any platform (including mobile devices), should allow everyone to enjoy it. The source code is released under the GNU AGPL to ensure that the users' freedoms are preserved even if this game is rendered or in any way run server-side.
2012-03-11 21:38:06 -04:00
/**
* Retrieve image data for each individual tile (pre-masked)
*
* Each tile will have the mask applied before being returned. This allows
* the tile to be rendered without any additional processing, but at the
* cost of additional overhead for the tile loading (which is well worth it,
* since we will be spending the majority of our time rendering tiles, not
* loading them).
*
* This operation is asynchronous, but the masking algorithm is not. If
* performance is a concern during the masking process (for example, if one
* were to create an extension to support very large tilesets), one can
* extend this class to make the operation asynchronous.
*
* @param {string} bmp_game game tileset bitmap (URL or data URL)
* @param {string} bmp_mask game tileset mask bitmap (URL or data URL)
*
* @param {function(Object)} callback function to call with tiles
*
* @return {ltjs.TileMasker} self
*/
'public getMaskedTiles': function( bmp_game, bmp_mask, callback )
{
var _self = this;
this._getImageData( bmp_mask, function( data_mask )
{
// we will render the game image after the mask so that it does not
// need to be re-rendered in order to pull out the image data
_self._renderImage( bmp_game, function()
{
_self.getMaskedTileSet( data_mask, callback );
} );
} );
return this;
},
/**
* For use by subtypes that may need access to the otherwise private data
*
* See getMaskedTileSet().
*
* @return {Array.<number>} tile width, height and number per row
*/
'protected getTileDimensions': function()
{
return [ this._tileWidth, this._tileHeight, this._tilesPerRow ];
},
Initial commit illustrating LTG loading and masking This project -- creating a LaserTank clone in JavaScript -- deserves some discussion before diving into the implementation details (you may skip to the separator below for implementation details without the history). LaserTank is a game that has a special place in my heart. Not only is it a witty and enjoyable game, but it is in fact the reason I began programming as a young boy in the first place. While it is likely that I would have eventually picked up a programming book for some other reason, I owe that point in time entirely to this game. Allow me to explain. At the age of 10, I would spend much of my time on the Internet downloading various games and demos that may satisfy my interests (the old days of CNET's download.com showed me many of those, I believe). One of those games that immediately captivated me was LaserTank, but not purely for reasons of gameplay. It had this wonderful feature that added so much potential (and replayability) to the game -- a map editor. I found myself enthralled with the map editor to the point where I spent all my time creating maps rather than playing the game. What fascinated me was the ability to essentially create portions of the game -- to tell the game what to do and how to function. I was not able to create my own game using this editor, but I felt like I was creating portions of it. That said, I soon realized that it wasn't enough; I needed to do more. It was the limitations of the map editor and the enjoyment of creating the maps that caused me to convince my parents to take me to Barnes and Noble to purchase my first programming book - Learn to Program with Visual Basic 6 by John Smiley (N.B. Visual Basic is proprietary and I cannot recommend using it. I had no knowledge of the evils of proprietary software back at that point in time.) My parents had their doubts, but that only pushed me even harder to learn. Ironically, the book was about creating a business application. I found this process very enjoyable and began focusing more on conventional desktop software rather than gaming. I did create a couple games (bop-a-mole and breakout among others), but my focus was never game development. Eventually, I moved on from Visual Basic and got into web development. Following that, I discovered GNU/Linux and began getting more and more into lower-level systems and languages (such as C and ASM) and began adopting the hacker ethic. That brings us to where I am today -- nearly 13 years later. It would only seem fitting to bring my hobby-turned-career full circle by cloning the very game that started everything. When I say "clone", I mean nothing more; there will be no additional features or modifications to the original gameplay, menus, graphics, etc. It will support all original file formats (I will develop none of my own). The only differences between the clone and the original game will arise from the obvious issues introduced by cloning the game on a web platform. Specifically, the user will have the option to load files from either their local box or a remote resource, and I may provide pre-masked tile sets for browsers that do not support the canvas element (a fallback mode, if you will). No matter what the change, though, the gameplay will remain identical. That said, the library resulting from the clone will be built with extensibility in mind. If a user (or myself) wishes to create a derivative work by hooking or extending the library (for example, to support larger maps, add additional blocks/enemies, multiplayer support, etc), that should be fairly trivial to do. However, those works will be entirely separate from the clone and clearly distinguished. I think the original LaserTank is perfect the way it is. Remember, it has a special place in my heart (aww) and I would like to preserve the game as I remember it back then. --- Alright; now that we have a great deal of unnecessary history out of the way, let's get into the implementation details for this commit (if you're reading this as a blog entry, see the first commit). This commit represents a proof-of-concept showing that the LTG files (containing the LaserTank graphics) can be properly loaded and their masks properly applied. This was the first major concern for the project and, if a workaround were needed, would have prevented me from creating a full clone (as it would not support loading LTG files without having them first sent to the server, processed, and returned in a different format). The LTG file contains some metadata (including the name, author and description) as well as two bitmaps (BMPs) -- the game tileset and the associated mask. The game bitmap's position was static, but its length and the offset of the mask bitmap were provided by the four bytes immediately preceding the game bitmap (the TLTGREC struct in LTANK.H of the original sources represented this value as a DWORD, which represents a 32-bit integer). The only challenge with converting this value into an integer that we could use is its endianness -- is the most significant byte at the beginning or end? Windows programs (of which LaserTank is) generally write in little-endian format, but to be sure we can simply open up the LTG file in your favorite HEX editor (I simply use `xxd`). In the case of the original tileset, the four bytes immediately preceding the bitmap header (as identified by 'BM', or `424d`) were `7a f5 00 00`, which on its own clearly indicates little-endianness. We can verify by searching for 'BM' once again, and finding that it begins at location `f5 7a` (if your HEX editor displays in big-endian format). To convert into a number, we can simply add up each byte individually, left-shifting by 8N bits, where N is the 0-indexed byte position. Loading the BMP files was then fairly trivial; the file could be loaded into memory (read from disk using FileReader) and we could cut the relevant portions of the binary string out. The bitmaps could then be base64-encoded and the "src" attribute of an Image object set to 'data:image/bmp;base64,B', where B is the base64-encoded BMP. This could then be rendered however we please - CSS sprites or to a canvas. The problem with CSS sprites is that we need to apply the mask and there is no reliable way to do this without a canvas; transparency in the browser is normally handled using GIFs or PNGs. As it turns out, the canvas performs masking using the alpha channel as well, so I would have to create my own masking algorithm to manipulate the alpha channel of the tileset. To complicate matters even more, certain tiles had no mask, and they did not consistently represent the mask with all black (black is used in LT to indicate opacity), meaning that the algorithm would have to understand what tiles should be skipped entirely. The solution was to simply loop through each tile and set the alpha byte of each pixel relative to the respective pixel on the map. Because the images were created out of data in memory, the canvas is not tainted when the image is rendered before using getImageData(). To help speed up the process, since we know that the mask can only contain black and white, we need only check one of the channels; we do not need to calculate brightness. This process successfully returns each individual tile, properly masked, which can be rendered to the canvas using putImageData(). Crisis averted. With that major concern out of the way, the clone should no longer be a problem. I go into more detail in the comments within LtgLoader and TileMasker. This is going to be an exciting process, both because of the LT clone itself and because this is my first experience working with the canvas element. As aforementioned, I seldom create games and I have had no use for the canvas thus far. Hopefully this project will be well-received by both those who have played LaserTank in the past and the original developer of the game (Jim Kindley, JEK Software). LaserTank is a fairly old game and is not likely to be well known anymore, but the game itself is a blast (no pun intended) and bringing it to the browser, where it can be used on any platform (including mobile devices), should allow everyone to enjoy it. The source code is released under the GNU AGPL to ensure that the users' freedoms are preserved even if this game is rendered or in any way run server-side.
2012-03-11 21:38:06 -04:00
/**
* Apply mask to each tile and return individual tiles
*
* This method requires that the tileset has already been rendered to the
* canvas.
*
* Note that, although this method accepts a callback, it is not
* asynchronous. It does, however, allow subtypes to make this algorithm
* asynchronous should the need arise. See getMaskedTiles() for more
* information.
*
* @param {Object} data_mask image data for mask bitmap
* @param {function(Object)} callback function to call with tiles
*
* @return {undefined}
*/
'virtual protected getMaskedTileSet': function( data_mask, callback )
{
2012-03-12 21:14:39 -04:00
var tdata = this._tileDfn,
Initial commit illustrating LTG loading and masking This project -- creating a LaserTank clone in JavaScript -- deserves some discussion before diving into the implementation details (you may skip to the separator below for implementation details without the history). LaserTank is a game that has a special place in my heart. Not only is it a witty and enjoyable game, but it is in fact the reason I began programming as a young boy in the first place. While it is likely that I would have eventually picked up a programming book for some other reason, I owe that point in time entirely to this game. Allow me to explain. At the age of 10, I would spend much of my time on the Internet downloading various games and demos that may satisfy my interests (the old days of CNET's download.com showed me many of those, I believe). One of those games that immediately captivated me was LaserTank, but not purely for reasons of gameplay. It had this wonderful feature that added so much potential (and replayability) to the game -- a map editor. I found myself enthralled with the map editor to the point where I spent all my time creating maps rather than playing the game. What fascinated me was the ability to essentially create portions of the game -- to tell the game what to do and how to function. I was not able to create my own game using this editor, but I felt like I was creating portions of it. That said, I soon realized that it wasn't enough; I needed to do more. It was the limitations of the map editor and the enjoyment of creating the maps that caused me to convince my parents to take me to Barnes and Noble to purchase my first programming book - Learn to Program with Visual Basic 6 by John Smiley (N.B. Visual Basic is proprietary and I cannot recommend using it. I had no knowledge of the evils of proprietary software back at that point in time.) My parents had their doubts, but that only pushed me even harder to learn. Ironically, the book was about creating a business application. I found this process very enjoyable and began focusing more on conventional desktop software rather than gaming. I did create a couple games (bop-a-mole and breakout among others), but my focus was never game development. Eventually, I moved on from Visual Basic and got into web development. Following that, I discovered GNU/Linux and began getting more and more into lower-level systems and languages (such as C and ASM) and began adopting the hacker ethic. That brings us to where I am today -- nearly 13 years later. It would only seem fitting to bring my hobby-turned-career full circle by cloning the very game that started everything. When I say "clone", I mean nothing more; there will be no additional features or modifications to the original gameplay, menus, graphics, etc. It will support all original file formats (I will develop none of my own). The only differences between the clone and the original game will arise from the obvious issues introduced by cloning the game on a web platform. Specifically, the user will have the option to load files from either their local box or a remote resource, and I may provide pre-masked tile sets for browsers that do not support the canvas element (a fallback mode, if you will). No matter what the change, though, the gameplay will remain identical. That said, the library resulting from the clone will be built with extensibility in mind. If a user (or myself) wishes to create a derivative work by hooking or extending the library (for example, to support larger maps, add additional blocks/enemies, multiplayer support, etc), that should be fairly trivial to do. However, those works will be entirely separate from the clone and clearly distinguished. I think the original LaserTank is perfect the way it is. Remember, it has a special place in my heart (aww) and I would like to preserve the game as I remember it back then. --- Alright; now that we have a great deal of unnecessary history out of the way, let's get into the implementation details for this commit (if you're reading this as a blog entry, see the first commit). This commit represents a proof-of-concept showing that the LTG files (containing the LaserTank graphics) can be properly loaded and their masks properly applied. This was the first major concern for the project and, if a workaround were needed, would have prevented me from creating a full clone (as it would not support loading LTG files without having them first sent to the server, processed, and returned in a different format). The LTG file contains some metadata (including the name, author and description) as well as two bitmaps (BMPs) -- the game tileset and the associated mask. The game bitmap's position was static, but its length and the offset of the mask bitmap were provided by the four bytes immediately preceding the game bitmap (the TLTGREC struct in LTANK.H of the original sources represented this value as a DWORD, which represents a 32-bit integer). The only challenge with converting this value into an integer that we could use is its endianness -- is the most significant byte at the beginning or end? Windows programs (of which LaserTank is) generally write in little-endian format, but to be sure we can simply open up the LTG file in your favorite HEX editor (I simply use `xxd`). In the case of the original tileset, the four bytes immediately preceding the bitmap header (as identified by 'BM', or `424d`) were `7a f5 00 00`, which on its own clearly indicates little-endianness. We can verify by searching for 'BM' once again, and finding that it begins at location `f5 7a` (if your HEX editor displays in big-endian format). To convert into a number, we can simply add up each byte individually, left-shifting by 8N bits, where N is the 0-indexed byte position. Loading the BMP files was then fairly trivial; the file could be loaded into memory (read from disk using FileReader) and we could cut the relevant portions of the binary string out. The bitmaps could then be base64-encoded and the "src" attribute of an Image object set to 'data:image/bmp;base64,B', where B is the base64-encoded BMP. This could then be rendered however we please - CSS sprites or to a canvas. The problem with CSS sprites is that we need to apply the mask and there is no reliable way to do this without a canvas; transparency in the browser is normally handled using GIFs or PNGs. As it turns out, the canvas performs masking using the alpha channel as well, so I would have to create my own masking algorithm to manipulate the alpha channel of the tileset. To complicate matters even more, certain tiles had no mask, and they did not consistently represent the mask with all black (black is used in LT to indicate opacity), meaning that the algorithm would have to understand what tiles should be skipped entirely. The solution was to simply loop through each tile and set the alpha byte of each pixel relative to the respective pixel on the map. Because the images were created out of data in memory, the canvas is not tainted when the image is rendered before using getImageData(). To help speed up the process, since we know that the mask can only contain black and white, we need only check one of the channels; we do not need to calculate brightness. This process successfully returns each individual tile, properly masked, which can be rendered to the canvas using putImageData(). Crisis averted. With that major concern out of the way, the clone should no longer be a problem. I go into more detail in the comments within LtgLoader and TileMasker. This is going to be an exciting process, both because of the LT clone itself and because this is my first experience working with the canvas element. As aforementioned, I seldom create games and I have had no use for the canvas thus far. Hopefully this project will be well-received by both those who have played LaserTank in the past and the original developer of the game (Jim Kindley, JEK Software). LaserTank is a fairly old game and is not likely to be well known anymore, but the game itself is a blast (no pun intended) and bringing it to the browser, where it can be used on any platform (including mobile devices), should allow everyone to enjoy it. The source code is released under the GNU AGPL to ensure that the users' freedoms are preserved even if this game is rendered or in any way run server-side.
2012-03-11 21:38:06 -04:00
tiles = {},
i = -1,
len = tdata.length,
2012-03-12 21:14:39 -04:00
// shorten the names
tw = this._tileWidth,
th = this._tileHeight,
xn = this._tilesPerRow;
Initial commit illustrating LTG loading and masking This project -- creating a LaserTank clone in JavaScript -- deserves some discussion before diving into the implementation details (you may skip to the separator below for implementation details without the history). LaserTank is a game that has a special place in my heart. Not only is it a witty and enjoyable game, but it is in fact the reason I began programming as a young boy in the first place. While it is likely that I would have eventually picked up a programming book for some other reason, I owe that point in time entirely to this game. Allow me to explain. At the age of 10, I would spend much of my time on the Internet downloading various games and demos that may satisfy my interests (the old days of CNET's download.com showed me many of those, I believe). One of those games that immediately captivated me was LaserTank, but not purely for reasons of gameplay. It had this wonderful feature that added so much potential (and replayability) to the game -- a map editor. I found myself enthralled with the map editor to the point where I spent all my time creating maps rather than playing the game. What fascinated me was the ability to essentially create portions of the game -- to tell the game what to do and how to function. I was not able to create my own game using this editor, but I felt like I was creating portions of it. That said, I soon realized that it wasn't enough; I needed to do more. It was the limitations of the map editor and the enjoyment of creating the maps that caused me to convince my parents to take me to Barnes and Noble to purchase my first programming book - Learn to Program with Visual Basic 6 by John Smiley (N.B. Visual Basic is proprietary and I cannot recommend using it. I had no knowledge of the evils of proprietary software back at that point in time.) My parents had their doubts, but that only pushed me even harder to learn. Ironically, the book was about creating a business application. I found this process very enjoyable and began focusing more on conventional desktop software rather than gaming. I did create a couple games (bop-a-mole and breakout among others), but my focus was never game development. Eventually, I moved on from Visual Basic and got into web development. Following that, I discovered GNU/Linux and began getting more and more into lower-level systems and languages (such as C and ASM) and began adopting the hacker ethic. That brings us to where I am today -- nearly 13 years later. It would only seem fitting to bring my hobby-turned-career full circle by cloning the very game that started everything. When I say "clone", I mean nothing more; there will be no additional features or modifications to the original gameplay, menus, graphics, etc. It will support all original file formats (I will develop none of my own). The only differences between the clone and the original game will arise from the obvious issues introduced by cloning the game on a web platform. Specifically, the user will have the option to load files from either their local box or a remote resource, and I may provide pre-masked tile sets for browsers that do not support the canvas element (a fallback mode, if you will). No matter what the change, though, the gameplay will remain identical. That said, the library resulting from the clone will be built with extensibility in mind. If a user (or myself) wishes to create a derivative work by hooking or extending the library (for example, to support larger maps, add additional blocks/enemies, multiplayer support, etc), that should be fairly trivial to do. However, those works will be entirely separate from the clone and clearly distinguished. I think the original LaserTank is perfect the way it is. Remember, it has a special place in my heart (aww) and I would like to preserve the game as I remember it back then. --- Alright; now that we have a great deal of unnecessary history out of the way, let's get into the implementation details for this commit (if you're reading this as a blog entry, see the first commit). This commit represents a proof-of-concept showing that the LTG files (containing the LaserTank graphics) can be properly loaded and their masks properly applied. This was the first major concern for the project and, if a workaround were needed, would have prevented me from creating a full clone (as it would not support loading LTG files without having them first sent to the server, processed, and returned in a different format). The LTG file contains some metadata (including the name, author and description) as well as two bitmaps (BMPs) -- the game tileset and the associated mask. The game bitmap's position was static, but its length and the offset of the mask bitmap were provided by the four bytes immediately preceding the game bitmap (the TLTGREC struct in LTANK.H of the original sources represented this value as a DWORD, which represents a 32-bit integer). The only challenge with converting this value into an integer that we could use is its endianness -- is the most significant byte at the beginning or end? Windows programs (of which LaserTank is) generally write in little-endian format, but to be sure we can simply open up the LTG file in your favorite HEX editor (I simply use `xxd`). In the case of the original tileset, the four bytes immediately preceding the bitmap header (as identified by 'BM', or `424d`) were `7a f5 00 00`, which on its own clearly indicates little-endianness. We can verify by searching for 'BM' once again, and finding that it begins at location `f5 7a` (if your HEX editor displays in big-endian format). To convert into a number, we can simply add up each byte individually, left-shifting by 8N bits, where N is the 0-indexed byte position. Loading the BMP files was then fairly trivial; the file could be loaded into memory (read from disk using FileReader) and we could cut the relevant portions of the binary string out. The bitmaps could then be base64-encoded and the "src" attribute of an Image object set to 'data:image/bmp;base64,B', where B is the base64-encoded BMP. This could then be rendered however we please - CSS sprites or to a canvas. The problem with CSS sprites is that we need to apply the mask and there is no reliable way to do this without a canvas; transparency in the browser is normally handled using GIFs or PNGs. As it turns out, the canvas performs masking using the alpha channel as well, so I would have to create my own masking algorithm to manipulate the alpha channel of the tileset. To complicate matters even more, certain tiles had no mask, and they did not consistently represent the mask with all black (black is used in LT to indicate opacity), meaning that the algorithm would have to understand what tiles should be skipped entirely. The solution was to simply loop through each tile and set the alpha byte of each pixel relative to the respective pixel on the map. Because the images were created out of data in memory, the canvas is not tainted when the image is rendered before using getImageData(). To help speed up the process, since we know that the mask can only contain black and white, we need only check one of the channels; we do not need to calculate brightness. This process successfully returns each individual tile, properly masked, which can be rendered to the canvas using putImageData(). Crisis averted. With that major concern out of the way, the clone should no longer be a problem. I go into more detail in the comments within LtgLoader and TileMasker. This is going to be an exciting process, both because of the LT clone itself and because this is my first experience working with the canvas element. As aforementioned, I seldom create games and I have had no use for the canvas thus far. Hopefully this project will be well-received by both those who have played LaserTank in the past and the original developer of the game (Jim Kindley, JEK Software). LaserTank is a fairly old game and is not likely to be well known anymore, but the game itself is a blast (no pun intended) and bringing it to the browser, where it can be used on any platform (including mobile devices), should allow everyone to enjoy it. The source code is released under the GNU AGPL to ensure that the users' freedoms are preserved even if this game is rendered or in any way run server-side.
2012-03-11 21:38:06 -04:00
// create each tile (preserving order, thus no decrementing)
while ( ++i < len )
{
var id = tdata[ i ][ 0 ],
Initial commit illustrating LTG loading and masking This project -- creating a LaserTank clone in JavaScript -- deserves some discussion before diving into the implementation details (you may skip to the separator below for implementation details without the history). LaserTank is a game that has a special place in my heart. Not only is it a witty and enjoyable game, but it is in fact the reason I began programming as a young boy in the first place. While it is likely that I would have eventually picked up a programming book for some other reason, I owe that point in time entirely to this game. Allow me to explain. At the age of 10, I would spend much of my time on the Internet downloading various games and demos that may satisfy my interests (the old days of CNET's download.com showed me many of those, I believe). One of those games that immediately captivated me was LaserTank, but not purely for reasons of gameplay. It had this wonderful feature that added so much potential (and replayability) to the game -- a map editor. I found myself enthralled with the map editor to the point where I spent all my time creating maps rather than playing the game. What fascinated me was the ability to essentially create portions of the game -- to tell the game what to do and how to function. I was not able to create my own game using this editor, but I felt like I was creating portions of it. That said, I soon realized that it wasn't enough; I needed to do more. It was the limitations of the map editor and the enjoyment of creating the maps that caused me to convince my parents to take me to Barnes and Noble to purchase my first programming book - Learn to Program with Visual Basic 6 by John Smiley (N.B. Visual Basic is proprietary and I cannot recommend using it. I had no knowledge of the evils of proprietary software back at that point in time.) My parents had their doubts, but that only pushed me even harder to learn. Ironically, the book was about creating a business application. I found this process very enjoyable and began focusing more on conventional desktop software rather than gaming. I did create a couple games (bop-a-mole and breakout among others), but my focus was never game development. Eventually, I moved on from Visual Basic and got into web development. Following that, I discovered GNU/Linux and began getting more and more into lower-level systems and languages (such as C and ASM) and began adopting the hacker ethic. That brings us to where I am today -- nearly 13 years later. It would only seem fitting to bring my hobby-turned-career full circle by cloning the very game that started everything. When I say "clone", I mean nothing more; there will be no additional features or modifications to the original gameplay, menus, graphics, etc. It will support all original file formats (I will develop none of my own). The only differences between the clone and the original game will arise from the obvious issues introduced by cloning the game on a web platform. Specifically, the user will have the option to load files from either their local box or a remote resource, and I may provide pre-masked tile sets for browsers that do not support the canvas element (a fallback mode, if you will). No matter what the change, though, the gameplay will remain identical. That said, the library resulting from the clone will be built with extensibility in mind. If a user (or myself) wishes to create a derivative work by hooking or extending the library (for example, to support larger maps, add additional blocks/enemies, multiplayer support, etc), that should be fairly trivial to do. However, those works will be entirely separate from the clone and clearly distinguished. I think the original LaserTank is perfect the way it is. Remember, it has a special place in my heart (aww) and I would like to preserve the game as I remember it back then. --- Alright; now that we have a great deal of unnecessary history out of the way, let's get into the implementation details for this commit (if you're reading this as a blog entry, see the first commit). This commit represents a proof-of-concept showing that the LTG files (containing the LaserTank graphics) can be properly loaded and their masks properly applied. This was the first major concern for the project and, if a workaround were needed, would have prevented me from creating a full clone (as it would not support loading LTG files without having them first sent to the server, processed, and returned in a different format). The LTG file contains some metadata (including the name, author and description) as well as two bitmaps (BMPs) -- the game tileset and the associated mask. The game bitmap's position was static, but its length and the offset of the mask bitmap were provided by the four bytes immediately preceding the game bitmap (the TLTGREC struct in LTANK.H of the original sources represented this value as a DWORD, which represents a 32-bit integer). The only challenge with converting this value into an integer that we could use is its endianness -- is the most significant byte at the beginning or end? Windows programs (of which LaserTank is) generally write in little-endian format, but to be sure we can simply open up the LTG file in your favorite HEX editor (I simply use `xxd`). In the case of the original tileset, the four bytes immediately preceding the bitmap header (as identified by 'BM', or `424d`) were `7a f5 00 00`, which on its own clearly indicates little-endianness. We can verify by searching for 'BM' once again, and finding that it begins at location `f5 7a` (if your HEX editor displays in big-endian format). To convert into a number, we can simply add up each byte individually, left-shifting by 8N bits, where N is the 0-indexed byte position. Loading the BMP files was then fairly trivial; the file could be loaded into memory (read from disk using FileReader) and we could cut the relevant portions of the binary string out. The bitmaps could then be base64-encoded and the "src" attribute of an Image object set to 'data:image/bmp;base64,B', where B is the base64-encoded BMP. This could then be rendered however we please - CSS sprites or to a canvas. The problem with CSS sprites is that we need to apply the mask and there is no reliable way to do this without a canvas; transparency in the browser is normally handled using GIFs or PNGs. As it turns out, the canvas performs masking using the alpha channel as well, so I would have to create my own masking algorithm to manipulate the alpha channel of the tileset. To complicate matters even more, certain tiles had no mask, and they did not consistently represent the mask with all black (black is used in LT to indicate opacity), meaning that the algorithm would have to understand what tiles should be skipped entirely. The solution was to simply loop through each tile and set the alpha byte of each pixel relative to the respective pixel on the map. Because the images were created out of data in memory, the canvas is not tainted when the image is rendered before using getImageData(). To help speed up the process, since we know that the mask can only contain black and white, we need only check one of the channels; we do not need to calculate brightness. This process successfully returns each individual tile, properly masked, which can be rendered to the canvas using putImageData(). Crisis averted. With that major concern out of the way, the clone should no longer be a problem. I go into more detail in the comments within LtgLoader and TileMasker. This is going to be an exciting process, both because of the LT clone itself and because this is my first experience working with the canvas element. As aforementioned, I seldom create games and I have had no use for the canvas thus far. Hopefully this project will be well-received by both those who have played LaserTank in the past and the original developer of the game (Jim Kindley, JEK Software). LaserTank is a fairly old game and is not likely to be well known anymore, but the game itself is a blast (no pun intended) and bringing it to the browser, where it can be used on any platform (including mobile devices), should allow everyone to enjoy it. The source code is released under the GNU AGPL to ensure that the users' freedoms are preserved even if this game is rendered or in any way run server-side.
2012-03-11 21:38:06 -04:00
mask = tdata[ i ][ 1 ],
// calculate the X and Y position of this tile based on the tile
// and bitmap dimensions
x = ( ( i % xn ) * th ),
y = ( ( Math.floor( i / xn ) ) * tw );
// the third index indicates whether or not a mask should be applied
// to the tile
Initial map rendering support The next major step for the clone is the loading and parsing of LVL files (maps). This is especially imporant for the purpose of ensuring that users can continue to use the maps the know and love (and possibly created themselves), rather than having to recreate them in the clone. The maps are concatenated into a single fixed-width file and loaded (in the original sources) into the TLEVEL struct (LTANK.H). The main thing to note about this data is the manner in which the object data (the playfield) is stored. The TPLAYFIELD type (defined as a 16x16 multidimensional signed char array) is formatted as [x][y], meaning that it is an array of *columns*, not rows. While at first confusing, this does not complicate matters any. This commit adds initial rendering support for the playfield via MapRender and is demonstrated in the same demo file as LtgLoader. After loading a tile set, the user can load an LVL file. The rendering is done using two canvas elements, the second of which is created by MapRender itself and overlayed atop of the original canvas. Unmasked tiles are rendered to the bottom canvas and masked tiles to the upper, allowing us to move game objects without having to redraw underlying tiles. This is also necessary as the context putImageData() method overwrites any existing data rather than leaving values corresponding to the transparent pixels in tact. This commit also added support for tunnel coloring - each set of tunnels has its own distinct color. The color values were taken from ColorList in LTANK2.C in the original sources rather than using CSS color names (e.g. 'red', 'blue', 'magenta') because different environments may assign slightly different colors to those values (as an example, FireFox does not use #00FF00 for 'green'). Tunnels themselves are identified by a bitmask (0x40), so we can get the tunnel id by XORing with that mask. The ids are incremented by two in the LVL data (e.g. 0x40 for index 1, 0x42 for index 2), so we can then divide by two and take that index in the color array. This color is used to fill the tile with a solid color in the lower canvas. We can then paint the tile on the upper canvas, which will allow the color of the lower canvas to peek through the transparent pixels. Animation support has not yet been added (but animations are still visible in the demo to the left of the map rendering). This is an exciting start, as we can clearly see what the game will look like in the browser. This commit also does not cover any additional level metadata (e.g. author, difficulty, hints); that will be addressed in future commits and added to the demo.
2012-03-15 23:30:20 -04:00
this.appendTileFrame( tiles, id, mask, ( mask === 1 )
Initial commit illustrating LTG loading and masking This project -- creating a LaserTank clone in JavaScript -- deserves some discussion before diving into the implementation details (you may skip to the separator below for implementation details without the history). LaserTank is a game that has a special place in my heart. Not only is it a witty and enjoyable game, but it is in fact the reason I began programming as a young boy in the first place. While it is likely that I would have eventually picked up a programming book for some other reason, I owe that point in time entirely to this game. Allow me to explain. At the age of 10, I would spend much of my time on the Internet downloading various games and demos that may satisfy my interests (the old days of CNET's download.com showed me many of those, I believe). One of those games that immediately captivated me was LaserTank, but not purely for reasons of gameplay. It had this wonderful feature that added so much potential (and replayability) to the game -- a map editor. I found myself enthralled with the map editor to the point where I spent all my time creating maps rather than playing the game. What fascinated me was the ability to essentially create portions of the game -- to tell the game what to do and how to function. I was not able to create my own game using this editor, but I felt like I was creating portions of it. That said, I soon realized that it wasn't enough; I needed to do more. It was the limitations of the map editor and the enjoyment of creating the maps that caused me to convince my parents to take me to Barnes and Noble to purchase my first programming book - Learn to Program with Visual Basic 6 by John Smiley (N.B. Visual Basic is proprietary and I cannot recommend using it. I had no knowledge of the evils of proprietary software back at that point in time.) My parents had their doubts, but that only pushed me even harder to learn. Ironically, the book was about creating a business application. I found this process very enjoyable and began focusing more on conventional desktop software rather than gaming. I did create a couple games (bop-a-mole and breakout among others), but my focus was never game development. Eventually, I moved on from Visual Basic and got into web development. Following that, I discovered GNU/Linux and began getting more and more into lower-level systems and languages (such as C and ASM) and began adopting the hacker ethic. That brings us to where I am today -- nearly 13 years later. It would only seem fitting to bring my hobby-turned-career full circle by cloning the very game that started everything. When I say "clone", I mean nothing more; there will be no additional features or modifications to the original gameplay, menus, graphics, etc. It will support all original file formats (I will develop none of my own). The only differences between the clone and the original game will arise from the obvious issues introduced by cloning the game on a web platform. Specifically, the user will have the option to load files from either their local box or a remote resource, and I may provide pre-masked tile sets for browsers that do not support the canvas element (a fallback mode, if you will). No matter what the change, though, the gameplay will remain identical. That said, the library resulting from the clone will be built with extensibility in mind. If a user (or myself) wishes to create a derivative work by hooking or extending the library (for example, to support larger maps, add additional blocks/enemies, multiplayer support, etc), that should be fairly trivial to do. However, those works will be entirely separate from the clone and clearly distinguished. I think the original LaserTank is perfect the way it is. Remember, it has a special place in my heart (aww) and I would like to preserve the game as I remember it back then. --- Alright; now that we have a great deal of unnecessary history out of the way, let's get into the implementation details for this commit (if you're reading this as a blog entry, see the first commit). This commit represents a proof-of-concept showing that the LTG files (containing the LaserTank graphics) can be properly loaded and their masks properly applied. This was the first major concern for the project and, if a workaround were needed, would have prevented me from creating a full clone (as it would not support loading LTG files without having them first sent to the server, processed, and returned in a different format). The LTG file contains some metadata (including the name, author and description) as well as two bitmaps (BMPs) -- the game tileset and the associated mask. The game bitmap's position was static, but its length and the offset of the mask bitmap were provided by the four bytes immediately preceding the game bitmap (the TLTGREC struct in LTANK.H of the original sources represented this value as a DWORD, which represents a 32-bit integer). The only challenge with converting this value into an integer that we could use is its endianness -- is the most significant byte at the beginning or end? Windows programs (of which LaserTank is) generally write in little-endian format, but to be sure we can simply open up the LTG file in your favorite HEX editor (I simply use `xxd`). In the case of the original tileset, the four bytes immediately preceding the bitmap header (as identified by 'BM', or `424d`) were `7a f5 00 00`, which on its own clearly indicates little-endianness. We can verify by searching for 'BM' once again, and finding that it begins at location `f5 7a` (if your HEX editor displays in big-endian format). To convert into a number, we can simply add up each byte individually, left-shifting by 8N bits, where N is the 0-indexed byte position. Loading the BMP files was then fairly trivial; the file could be loaded into memory (read from disk using FileReader) and we could cut the relevant portions of the binary string out. The bitmaps could then be base64-encoded and the "src" attribute of an Image object set to 'data:image/bmp;base64,B', where B is the base64-encoded BMP. This could then be rendered however we please - CSS sprites or to a canvas. The problem with CSS sprites is that we need to apply the mask and there is no reliable way to do this without a canvas; transparency in the browser is normally handled using GIFs or PNGs. As it turns out, the canvas performs masking using the alpha channel as well, so I would have to create my own masking algorithm to manipulate the alpha channel of the tileset. To complicate matters even more, certain tiles had no mask, and they did not consistently represent the mask with all black (black is used in LT to indicate opacity), meaning that the algorithm would have to understand what tiles should be skipped entirely. The solution was to simply loop through each tile and set the alpha byte of each pixel relative to the respective pixel on the map. Because the images were created out of data in memory, the canvas is not tainted when the image is rendered before using getImageData(). To help speed up the process, since we know that the mask can only contain black and white, we need only check one of the channels; we do not need to calculate brightness. This process successfully returns each individual tile, properly masked, which can be rendered to the canvas using putImageData(). Crisis averted. With that major concern out of the way, the clone should no longer be a problem. I go into more detail in the comments within LtgLoader and TileMasker. This is going to be an exciting process, both because of the LT clone itself and because this is my first experience working with the canvas element. As aforementioned, I seldom create games and I have had no use for the canvas thus far. Hopefully this project will be well-received by both those who have played LaserTank in the past and the original developer of the game (Jim Kindley, JEK Software). LaserTank is a fairly old game and is not likely to be well known anymore, but the game itself is a blast (no pun intended) and bringing it to the browser, where it can be used on any platform (including mobile devices), should allow everyone to enjoy it. The source code is released under the GNU AGPL to ensure that the users' freedoms are preserved even if this game is rendered or in any way run server-side.
2012-03-11 21:38:06 -04:00
? this.getMaskedTileData( data_mask, x, y )
: this.getTileData( x, y )
);
Initial commit illustrating LTG loading and masking This project -- creating a LaserTank clone in JavaScript -- deserves some discussion before diving into the implementation details (you may skip to the separator below for implementation details without the history). LaserTank is a game that has a special place in my heart. Not only is it a witty and enjoyable game, but it is in fact the reason I began programming as a young boy in the first place. While it is likely that I would have eventually picked up a programming book for some other reason, I owe that point in time entirely to this game. Allow me to explain. At the age of 10, I would spend much of my time on the Internet downloading various games and demos that may satisfy my interests (the old days of CNET's download.com showed me many of those, I believe). One of those games that immediately captivated me was LaserTank, but not purely for reasons of gameplay. It had this wonderful feature that added so much potential (and replayability) to the game -- a map editor. I found myself enthralled with the map editor to the point where I spent all my time creating maps rather than playing the game. What fascinated me was the ability to essentially create portions of the game -- to tell the game what to do and how to function. I was not able to create my own game using this editor, but I felt like I was creating portions of it. That said, I soon realized that it wasn't enough; I needed to do more. It was the limitations of the map editor and the enjoyment of creating the maps that caused me to convince my parents to take me to Barnes and Noble to purchase my first programming book - Learn to Program with Visual Basic 6 by John Smiley (N.B. Visual Basic is proprietary and I cannot recommend using it. I had no knowledge of the evils of proprietary software back at that point in time.) My parents had their doubts, but that only pushed me even harder to learn. Ironically, the book was about creating a business application. I found this process very enjoyable and began focusing more on conventional desktop software rather than gaming. I did create a couple games (bop-a-mole and breakout among others), but my focus was never game development. Eventually, I moved on from Visual Basic and got into web development. Following that, I discovered GNU/Linux and began getting more and more into lower-level systems and languages (such as C and ASM) and began adopting the hacker ethic. That brings us to where I am today -- nearly 13 years later. It would only seem fitting to bring my hobby-turned-career full circle by cloning the very game that started everything. When I say "clone", I mean nothing more; there will be no additional features or modifications to the original gameplay, menus, graphics, etc. It will support all original file formats (I will develop none of my own). The only differences between the clone and the original game will arise from the obvious issues introduced by cloning the game on a web platform. Specifically, the user will have the option to load files from either their local box or a remote resource, and I may provide pre-masked tile sets for browsers that do not support the canvas element (a fallback mode, if you will). No matter what the change, though, the gameplay will remain identical. That said, the library resulting from the clone will be built with extensibility in mind. If a user (or myself) wishes to create a derivative work by hooking or extending the library (for example, to support larger maps, add additional blocks/enemies, multiplayer support, etc), that should be fairly trivial to do. However, those works will be entirely separate from the clone and clearly distinguished. I think the original LaserTank is perfect the way it is. Remember, it has a special place in my heart (aww) and I would like to preserve the game as I remember it back then. --- Alright; now that we have a great deal of unnecessary history out of the way, let's get into the implementation details for this commit (if you're reading this as a blog entry, see the first commit). This commit represents a proof-of-concept showing that the LTG files (containing the LaserTank graphics) can be properly loaded and their masks properly applied. This was the first major concern for the project and, if a workaround were needed, would have prevented me from creating a full clone (as it would not support loading LTG files without having them first sent to the server, processed, and returned in a different format). The LTG file contains some metadata (including the name, author and description) as well as two bitmaps (BMPs) -- the game tileset and the associated mask. The game bitmap's position was static, but its length and the offset of the mask bitmap were provided by the four bytes immediately preceding the game bitmap (the TLTGREC struct in LTANK.H of the original sources represented this value as a DWORD, which represents a 32-bit integer). The only challenge with converting this value into an integer that we could use is its endianness -- is the most significant byte at the beginning or end? Windows programs (of which LaserTank is) generally write in little-endian format, but to be sure we can simply open up the LTG file in your favorite HEX editor (I simply use `xxd`). In the case of the original tileset, the four bytes immediately preceding the bitmap header (as identified by 'BM', or `424d`) were `7a f5 00 00`, which on its own clearly indicates little-endianness. We can verify by searching for 'BM' once again, and finding that it begins at location `f5 7a` (if your HEX editor displays in big-endian format). To convert into a number, we can simply add up each byte individually, left-shifting by 8N bits, where N is the 0-indexed byte position. Loading the BMP files was then fairly trivial; the file could be loaded into memory (read from disk using FileReader) and we could cut the relevant portions of the binary string out. The bitmaps could then be base64-encoded and the "src" attribute of an Image object set to 'data:image/bmp;base64,B', where B is the base64-encoded BMP. This could then be rendered however we please - CSS sprites or to a canvas. The problem with CSS sprites is that we need to apply the mask and there is no reliable way to do this without a canvas; transparency in the browser is normally handled using GIFs or PNGs. As it turns out, the canvas performs masking using the alpha channel as well, so I would have to create my own masking algorithm to manipulate the alpha channel of the tileset. To complicate matters even more, certain tiles had no mask, and they did not consistently represent the mask with all black (black is used in LT to indicate opacity), meaning that the algorithm would have to understand what tiles should be skipped entirely. The solution was to simply loop through each tile and set the alpha byte of each pixel relative to the respective pixel on the map. Because the images were created out of data in memory, the canvas is not tainted when the image is rendered before using getImageData(). To help speed up the process, since we know that the mask can only contain black and white, we need only check one of the channels; we do not need to calculate brightness. This process successfully returns each individual tile, properly masked, which can be rendered to the canvas using putImageData(). Crisis averted. With that major concern out of the way, the clone should no longer be a problem. I go into more detail in the comments within LtgLoader and TileMasker. This is going to be an exciting process, both because of the LT clone itself and because this is my first experience working with the canvas element. As aforementioned, I seldom create games and I have had no use for the canvas thus far. Hopefully this project will be well-received by both those who have played LaserTank in the past and the original developer of the game (Jim Kindley, JEK Software). LaserTank is a fairly old game and is not likely to be well known anymore, but the game itself is a blast (no pun intended) and bringing it to the browser, where it can be used on any platform (including mobile devices), should allow everyone to enjoy it. The source code is released under the GNU AGPL to ensure that the users' freedoms are preserved even if this game is rendered or in any way run server-side.
2012-03-11 21:38:06 -04:00
}
callback( tiles );
},
/**
* Adds a tile frame to the set, permitting animation
*
* This creates a circular linked list with each of the frames for a given
* tile id. This structure is ideal for looping animations. One can simply
* check to see if the tile has a single frame by performing a strict
* equality check on the current tile and its 'next' entry.
*
* @param {Object.<string>} set set to append tile frame to
* @param {string} id tile id
* @param {Object} data tile ImageData
*
* @return {undefined}
*/
Initial map rendering support The next major step for the clone is the loading and parsing of LVL files (maps). This is especially imporant for the purpose of ensuring that users can continue to use the maps the know and love (and possibly created themselves), rather than having to recreate them in the clone. The maps are concatenated into a single fixed-width file and loaded (in the original sources) into the TLEVEL struct (LTANK.H). The main thing to note about this data is the manner in which the object data (the playfield) is stored. The TPLAYFIELD type (defined as a 16x16 multidimensional signed char array) is formatted as [x][y], meaning that it is an array of *columns*, not rows. While at first confusing, this does not complicate matters any. This commit adds initial rendering support for the playfield via MapRender and is demonstrated in the same demo file as LtgLoader. After loading a tile set, the user can load an LVL file. The rendering is done using two canvas elements, the second of which is created by MapRender itself and overlayed atop of the original canvas. Unmasked tiles are rendered to the bottom canvas and masked tiles to the upper, allowing us to move game objects without having to redraw underlying tiles. This is also necessary as the context putImageData() method overwrites any existing data rather than leaving values corresponding to the transparent pixels in tact. This commit also added support for tunnel coloring - each set of tunnels has its own distinct color. The color values were taken from ColorList in LTANK2.C in the original sources rather than using CSS color names (e.g. 'red', 'blue', 'magenta') because different environments may assign slightly different colors to those values (as an example, FireFox does not use #00FF00 for 'green'). Tunnels themselves are identified by a bitmask (0x40), so we can get the tunnel id by XORing with that mask. The ids are incremented by two in the LVL data (e.g. 0x40 for index 1, 0x42 for index 2), so we can then divide by two and take that index in the color array. This color is used to fill the tile with a solid color in the lower canvas. We can then paint the tile on the upper canvas, which will allow the color of the lower canvas to peek through the transparent pixels. Animation support has not yet been added (but animations are still visible in the demo to the left of the map rendering). This is an exciting start, as we can clearly see what the game will look like in the browser. This commit also does not cover any additional level metadata (e.g. author, difficulty, hints); that will be addressed in future commits and added to the demo.
2012-03-15 23:30:20 -04:00
'protected appendTileFrame': function( set, id, mask, data )
{
var prev = set[ id ];
Initial map rendering support The next major step for the clone is the loading and parsing of LVL files (maps). This is especially imporant for the purpose of ensuring that users can continue to use the maps the know and love (and possibly created themselves), rather than having to recreate them in the clone. The maps are concatenated into a single fixed-width file and loaded (in the original sources) into the TLEVEL struct (LTANK.H). The main thing to note about this data is the manner in which the object data (the playfield) is stored. The TPLAYFIELD type (defined as a 16x16 multidimensional signed char array) is formatted as [x][y], meaning that it is an array of *columns*, not rows. While at first confusing, this does not complicate matters any. This commit adds initial rendering support for the playfield via MapRender and is demonstrated in the same demo file as LtgLoader. After loading a tile set, the user can load an LVL file. The rendering is done using two canvas elements, the second of which is created by MapRender itself and overlayed atop of the original canvas. Unmasked tiles are rendered to the bottom canvas and masked tiles to the upper, allowing us to move game objects without having to redraw underlying tiles. This is also necessary as the context putImageData() method overwrites any existing data rather than leaving values corresponding to the transparent pixels in tact. This commit also added support for tunnel coloring - each set of tunnels has its own distinct color. The color values were taken from ColorList in LTANK2.C in the original sources rather than using CSS color names (e.g. 'red', 'blue', 'magenta') because different environments may assign slightly different colors to those values (as an example, FireFox does not use #00FF00 for 'green'). Tunnels themselves are identified by a bitmask (0x40), so we can get the tunnel id by XORing with that mask. The ids are incremented by two in the LVL data (e.g. 0x40 for index 1, 0x42 for index 2), so we can then divide by two and take that index in the color array. This color is used to fill the tile with a solid color in the lower canvas. We can then paint the tile on the upper canvas, which will allow the color of the lower canvas to peek through the transparent pixels. Animation support has not yet been added (but animations are still visible in the demo to the left of the map rendering). This is an exciting start, as we can clearly see what the game will look like in the browser. This commit also does not cover any additional level metadata (e.g. author, difficulty, hints); that will be addressed in future commits and added to the demo.
2012-03-15 23:30:20 -04:00
set[ id ] = {
data: data,
masked: mask
};
// If there is a previous frame, set the 'next' entry to its 'next'
// entry to maintain the circular reference. Otherwise, set to self.
// This can also be stored as the 'first' reference to permit quickly
// resetting an animation.
set[ id ].next = set[ id ].first = ( prev )
? prev.next
: set[ id ];
// if there was a previous entry, set its 'next' entry to our new frame,
// expanding the linked list
prev && ( prev.next = set[ id ] )
},
Initial commit illustrating LTG loading and masking This project -- creating a LaserTank clone in JavaScript -- deserves some discussion before diving into the implementation details (you may skip to the separator below for implementation details without the history). LaserTank is a game that has a special place in my heart. Not only is it a witty and enjoyable game, but it is in fact the reason I began programming as a young boy in the first place. While it is likely that I would have eventually picked up a programming book for some other reason, I owe that point in time entirely to this game. Allow me to explain. At the age of 10, I would spend much of my time on the Internet downloading various games and demos that may satisfy my interests (the old days of CNET's download.com showed me many of those, I believe). One of those games that immediately captivated me was LaserTank, but not purely for reasons of gameplay. It had this wonderful feature that added so much potential (and replayability) to the game -- a map editor. I found myself enthralled with the map editor to the point where I spent all my time creating maps rather than playing the game. What fascinated me was the ability to essentially create portions of the game -- to tell the game what to do and how to function. I was not able to create my own game using this editor, but I felt like I was creating portions of it. That said, I soon realized that it wasn't enough; I needed to do more. It was the limitations of the map editor and the enjoyment of creating the maps that caused me to convince my parents to take me to Barnes and Noble to purchase my first programming book - Learn to Program with Visual Basic 6 by John Smiley (N.B. Visual Basic is proprietary and I cannot recommend using it. I had no knowledge of the evils of proprietary software back at that point in time.) My parents had their doubts, but that only pushed me even harder to learn. Ironically, the book was about creating a business application. I found this process very enjoyable and began focusing more on conventional desktop software rather than gaming. I did create a couple games (bop-a-mole and breakout among others), but my focus was never game development. Eventually, I moved on from Visual Basic and got into web development. Following that, I discovered GNU/Linux and began getting more and more into lower-level systems and languages (such as C and ASM) and began adopting the hacker ethic. That brings us to where I am today -- nearly 13 years later. It would only seem fitting to bring my hobby-turned-career full circle by cloning the very game that started everything. When I say "clone", I mean nothing more; there will be no additional features or modifications to the original gameplay, menus, graphics, etc. It will support all original file formats (I will develop none of my own). The only differences between the clone and the original game will arise from the obvious issues introduced by cloning the game on a web platform. Specifically, the user will have the option to load files from either their local box or a remote resource, and I may provide pre-masked tile sets for browsers that do not support the canvas element (a fallback mode, if you will). No matter what the change, though, the gameplay will remain identical. That said, the library resulting from the clone will be built with extensibility in mind. If a user (or myself) wishes to create a derivative work by hooking or extending the library (for example, to support larger maps, add additional blocks/enemies, multiplayer support, etc), that should be fairly trivial to do. However, those works will be entirely separate from the clone and clearly distinguished. I think the original LaserTank is perfect the way it is. Remember, it has a special place in my heart (aww) and I would like to preserve the game as I remember it back then. --- Alright; now that we have a great deal of unnecessary history out of the way, let's get into the implementation details for this commit (if you're reading this as a blog entry, see the first commit). This commit represents a proof-of-concept showing that the LTG files (containing the LaserTank graphics) can be properly loaded and their masks properly applied. This was the first major concern for the project and, if a workaround were needed, would have prevented me from creating a full clone (as it would not support loading LTG files without having them first sent to the server, processed, and returned in a different format). The LTG file contains some metadata (including the name, author and description) as well as two bitmaps (BMPs) -- the game tileset and the associated mask. The game bitmap's position was static, but its length and the offset of the mask bitmap were provided by the four bytes immediately preceding the game bitmap (the TLTGREC struct in LTANK.H of the original sources represented this value as a DWORD, which represents a 32-bit integer). The only challenge with converting this value into an integer that we could use is its endianness -- is the most significant byte at the beginning or end? Windows programs (of which LaserTank is) generally write in little-endian format, but to be sure we can simply open up the LTG file in your favorite HEX editor (I simply use `xxd`). In the case of the original tileset, the four bytes immediately preceding the bitmap header (as identified by 'BM', or `424d`) were `7a f5 00 00`, which on its own clearly indicates little-endianness. We can verify by searching for 'BM' once again, and finding that it begins at location `f5 7a` (if your HEX editor displays in big-endian format). To convert into a number, we can simply add up each byte individually, left-shifting by 8N bits, where N is the 0-indexed byte position. Loading the BMP files was then fairly trivial; the file could be loaded into memory (read from disk using FileReader) and we could cut the relevant portions of the binary string out. The bitmaps could then be base64-encoded and the "src" attribute of an Image object set to 'data:image/bmp;base64,B', where B is the base64-encoded BMP. This could then be rendered however we please - CSS sprites or to a canvas. The problem with CSS sprites is that we need to apply the mask and there is no reliable way to do this without a canvas; transparency in the browser is normally handled using GIFs or PNGs. As it turns out, the canvas performs masking using the alpha channel as well, so I would have to create my own masking algorithm to manipulate the alpha channel of the tileset. To complicate matters even more, certain tiles had no mask, and they did not consistently represent the mask with all black (black is used in LT to indicate opacity), meaning that the algorithm would have to understand what tiles should be skipped entirely. The solution was to simply loop through each tile and set the alpha byte of each pixel relative to the respective pixel on the map. Because the images were created out of data in memory, the canvas is not tainted when the image is rendered before using getImageData(). To help speed up the process, since we know that the mask can only contain black and white, we need only check one of the channels; we do not need to calculate brightness. This process successfully returns each individual tile, properly masked, which can be rendered to the canvas using putImageData(). Crisis averted. With that major concern out of the way, the clone should no longer be a problem. I go into more detail in the comments within LtgLoader and TileMasker. This is going to be an exciting process, both because of the LT clone itself and because this is my first experience working with the canvas element. As aforementioned, I seldom create games and I have had no use for the canvas thus far. Hopefully this project will be well-received by both those who have played LaserTank in the past and the original developer of the game (Jim Kindley, JEK Software). LaserTank is a fairly old game and is not likely to be well known anymore, but the game itself is a blast (no pun intended) and bringing it to the browser, where it can be used on any platform (including mobile devices), should allow everyone to enjoy it. The source code is released under the GNU AGPL to ensure that the users' freedoms are preserved even if this game is rendered or in any way run server-side.
2012-03-11 21:38:06 -04:00
/**
* Retrieve a tile with the mask applied
*
* This algorithm uses the image rendered to the canvas along with the given
* mask image data to alter the alpha channel of the tile, producing a tile
* with the appropriate transparency.
*
* The LaserTank mask considered black to be opaque and white to be
* transparent. Since those are the only two colors permitted, we can
* improve performance by checking only a single channel rather than
* calculating brightness. If not black, the respective pixel in the tile
* will be considered transparent.
*
* Only the image data for the requested tile will be returned. That is, the
* image data will represent a single tile and it can be rendered directly
* to the canvas.
*
* WARNING: Not all tiles have masks. This method should not be used unless
* the tile has a mask. The result is otherwise LTG-dependent, since some
* LTG files do not have fully opaque masks for those tiles.
*
* @param {Object} data_mask image data for the mask bitmap
* @param {number} x tile X position in game/mask bitmap
* @param {number} y tile Y position in game/mask bitmap
*
* @return {Object} image data for the requested tile
*/
'virtual protected getMaskedTileData': function( data_mask, x, y )
{
var raw = this.getTileData( x, y ),
w = raw.width,
h = raw.height,
mw = data_mask.width,
yi = h;
// apply the mask to the raw tile data (simple and easy-to-understand
// algorithm; we can refine it later if need be), looping through each
// pixel
while ( yi-- )
{
xi = w;
while ( xi-- )
{
// get the R value for the associated pixel in the mask bitmap
// (remember that, although we are dealing with applying the
// mask to a single tile, the mask image contains all tiles, so
// we must calculate its position accordingly)
var mi = ( ( ( yi + y ) * ( mw * 4 ) ) + ( ( xi + x ) * 4 ) ),
mr = data_mask.data[ mi ];
// manipulate the alpha channel of our tile; if the R value for
// the mask is not 0, then this pixel in our tile should be
// transparent (we need only check the R pixel since the mask
// consists of only black and white, so there is no need to
// calculate brightness)
raw.data[ ( ( yi * w * 4 ) + ( xi * 4 ) ) + 3 ] =
( mr === 0 ) ? 255 : 0;
}
}
return raw;
},
/**
* Retrieve image data for the tile at the given position
*
* @param {number} x tile X position in bitmap
* @param {number} y tile Y position in bitmap
*
* @return {Object} image data for tile
**/
'protected getTileData': function( x, y )
{
2012-03-12 21:14:39 -04:00
return this._context.getImageData(
x, y, this._tileWidth, this._tileHeight
);
Initial commit illustrating LTG loading and masking This project -- creating a LaserTank clone in JavaScript -- deserves some discussion before diving into the implementation details (you may skip to the separator below for implementation details without the history). LaserTank is a game that has a special place in my heart. Not only is it a witty and enjoyable game, but it is in fact the reason I began programming as a young boy in the first place. While it is likely that I would have eventually picked up a programming book for some other reason, I owe that point in time entirely to this game. Allow me to explain. At the age of 10, I would spend much of my time on the Internet downloading various games and demos that may satisfy my interests (the old days of CNET's download.com showed me many of those, I believe). One of those games that immediately captivated me was LaserTank, but not purely for reasons of gameplay. It had this wonderful feature that added so much potential (and replayability) to the game -- a map editor. I found myself enthralled with the map editor to the point where I spent all my time creating maps rather than playing the game. What fascinated me was the ability to essentially create portions of the game -- to tell the game what to do and how to function. I was not able to create my own game using this editor, but I felt like I was creating portions of it. That said, I soon realized that it wasn't enough; I needed to do more. It was the limitations of the map editor and the enjoyment of creating the maps that caused me to convince my parents to take me to Barnes and Noble to purchase my first programming book - Learn to Program with Visual Basic 6 by John Smiley (N.B. Visual Basic is proprietary and I cannot recommend using it. I had no knowledge of the evils of proprietary software back at that point in time.) My parents had their doubts, but that only pushed me even harder to learn. Ironically, the book was about creating a business application. I found this process very enjoyable and began focusing more on conventional desktop software rather than gaming. I did create a couple games (bop-a-mole and breakout among others), but my focus was never game development. Eventually, I moved on from Visual Basic and got into web development. Following that, I discovered GNU/Linux and began getting more and more into lower-level systems and languages (such as C and ASM) and began adopting the hacker ethic. That brings us to where I am today -- nearly 13 years later. It would only seem fitting to bring my hobby-turned-career full circle by cloning the very game that started everything. When I say "clone", I mean nothing more; there will be no additional features or modifications to the original gameplay, menus, graphics, etc. It will support all original file formats (I will develop none of my own). The only differences between the clone and the original game will arise from the obvious issues introduced by cloning the game on a web platform. Specifically, the user will have the option to load files from either their local box or a remote resource, and I may provide pre-masked tile sets for browsers that do not support the canvas element (a fallback mode, if you will). No matter what the change, though, the gameplay will remain identical. That said, the library resulting from the clone will be built with extensibility in mind. If a user (or myself) wishes to create a derivative work by hooking or extending the library (for example, to support larger maps, add additional blocks/enemies, multiplayer support, etc), that should be fairly trivial to do. However, those works will be entirely separate from the clone and clearly distinguished. I think the original LaserTank is perfect the way it is. Remember, it has a special place in my heart (aww) and I would like to preserve the game as I remember it back then. --- Alright; now that we have a great deal of unnecessary history out of the way, let's get into the implementation details for this commit (if you're reading this as a blog entry, see the first commit). This commit represents a proof-of-concept showing that the LTG files (containing the LaserTank graphics) can be properly loaded and their masks properly applied. This was the first major concern for the project and, if a workaround were needed, would have prevented me from creating a full clone (as it would not support loading LTG files without having them first sent to the server, processed, and returned in a different format). The LTG file contains some metadata (including the name, author and description) as well as two bitmaps (BMPs) -- the game tileset and the associated mask. The game bitmap's position was static, but its length and the offset of the mask bitmap were provided by the four bytes immediately preceding the game bitmap (the TLTGREC struct in LTANK.H of the original sources represented this value as a DWORD, which represents a 32-bit integer). The only challenge with converting this value into an integer that we could use is its endianness -- is the most significant byte at the beginning or end? Windows programs (of which LaserTank is) generally write in little-endian format, but to be sure we can simply open up the LTG file in your favorite HEX editor (I simply use `xxd`). In the case of the original tileset, the four bytes immediately preceding the bitmap header (as identified by 'BM', or `424d`) were `7a f5 00 00`, which on its own clearly indicates little-endianness. We can verify by searching for 'BM' once again, and finding that it begins at location `f5 7a` (if your HEX editor displays in big-endian format). To convert into a number, we can simply add up each byte individually, left-shifting by 8N bits, where N is the 0-indexed byte position. Loading the BMP files was then fairly trivial; the file could be loaded into memory (read from disk using FileReader) and we could cut the relevant portions of the binary string out. The bitmaps could then be base64-encoded and the "src" attribute of an Image object set to 'data:image/bmp;base64,B', where B is the base64-encoded BMP. This could then be rendered however we please - CSS sprites or to a canvas. The problem with CSS sprites is that we need to apply the mask and there is no reliable way to do this without a canvas; transparency in the browser is normally handled using GIFs or PNGs. As it turns out, the canvas performs masking using the alpha channel as well, so I would have to create my own masking algorithm to manipulate the alpha channel of the tileset. To complicate matters even more, certain tiles had no mask, and they did not consistently represent the mask with all black (black is used in LT to indicate opacity), meaning that the algorithm would have to understand what tiles should be skipped entirely. The solution was to simply loop through each tile and set the alpha byte of each pixel relative to the respective pixel on the map. Because the images were created out of data in memory, the canvas is not tainted when the image is rendered before using getImageData(). To help speed up the process, since we know that the mask can only contain black and white, we need only check one of the channels; we do not need to calculate brightness. This process successfully returns each individual tile, properly masked, which can be rendered to the canvas using putImageData(). Crisis averted. With that major concern out of the way, the clone should no longer be a problem. I go into more detail in the comments within LtgLoader and TileMasker. This is going to be an exciting process, both because of the LT clone itself and because this is my first experience working with the canvas element. As aforementioned, I seldom create games and I have had no use for the canvas thus far. Hopefully this project will be well-received by both those who have played LaserTank in the past and the original developer of the game (Jim Kindley, JEK Software). LaserTank is a fairly old game and is not likely to be well known anymore, but the game itself is a blast (no pun intended) and bringing it to the browser, where it can be used on any platform (including mobile devices), should allow everyone to enjoy it. The source code is released under the GNU AGPL to ensure that the users' freedoms are preserved even if this game is rendered or in any way run server-side.
2012-03-11 21:38:06 -04:00
},
/**
* Render an image to the canvas
*
* This operation is asynchronous and supports loading of external
* resources. Note that an external resource that violates the browser's
* cross-site security policies will taint the canvas, preventing the
* masking operation. Using data URLs will avoid this issue entirely.
*
* @param {string} bmp image URL or data URL
* @param {function(Image)} callback function to call when complete
*
* @return {undefined}
*/
'private _renderImage': function( bmp, callback )
{
var _self = this,
img = new Image();
img.onload = function()
{
_self._context.drawImage( img, 0, 0 );
callback( img );
};
img.src = bmp;
},
/**
* Retrieve the canvas image data of the given bitmap
*
* @param {string} bmp image URL or data URL
* @param {function(Image)} callback function to call when complete
*
* @return {undefined}
*/
'private _getImageData': function( bmp, callback )
{
var _self = this;
this._renderImage( bmp, function()
{
2012-03-12 21:14:39 -04:00
callback(
_self._context.getImageData(
0, 0, _self._setWidth, _self._setHeight
)
);
Initial commit illustrating LTG loading and masking This project -- creating a LaserTank clone in JavaScript -- deserves some discussion before diving into the implementation details (you may skip to the separator below for implementation details without the history). LaserTank is a game that has a special place in my heart. Not only is it a witty and enjoyable game, but it is in fact the reason I began programming as a young boy in the first place. While it is likely that I would have eventually picked up a programming book for some other reason, I owe that point in time entirely to this game. Allow me to explain. At the age of 10, I would spend much of my time on the Internet downloading various games and demos that may satisfy my interests (the old days of CNET's download.com showed me many of those, I believe). One of those games that immediately captivated me was LaserTank, but not purely for reasons of gameplay. It had this wonderful feature that added so much potential (and replayability) to the game -- a map editor. I found myself enthralled with the map editor to the point where I spent all my time creating maps rather than playing the game. What fascinated me was the ability to essentially create portions of the game -- to tell the game what to do and how to function. I was not able to create my own game using this editor, but I felt like I was creating portions of it. That said, I soon realized that it wasn't enough; I needed to do more. It was the limitations of the map editor and the enjoyment of creating the maps that caused me to convince my parents to take me to Barnes and Noble to purchase my first programming book - Learn to Program with Visual Basic 6 by John Smiley (N.B. Visual Basic is proprietary and I cannot recommend using it. I had no knowledge of the evils of proprietary software back at that point in time.) My parents had their doubts, but that only pushed me even harder to learn. Ironically, the book was about creating a business application. I found this process very enjoyable and began focusing more on conventional desktop software rather than gaming. I did create a couple games (bop-a-mole and breakout among others), but my focus was never game development. Eventually, I moved on from Visual Basic and got into web development. Following that, I discovered GNU/Linux and began getting more and more into lower-level systems and languages (such as C and ASM) and began adopting the hacker ethic. That brings us to where I am today -- nearly 13 years later. It would only seem fitting to bring my hobby-turned-career full circle by cloning the very game that started everything. When I say "clone", I mean nothing more; there will be no additional features or modifications to the original gameplay, menus, graphics, etc. It will support all original file formats (I will develop none of my own). The only differences between the clone and the original game will arise from the obvious issues introduced by cloning the game on a web platform. Specifically, the user will have the option to load files from either their local box or a remote resource, and I may provide pre-masked tile sets for browsers that do not support the canvas element (a fallback mode, if you will). No matter what the change, though, the gameplay will remain identical. That said, the library resulting from the clone will be built with extensibility in mind. If a user (or myself) wishes to create a derivative work by hooking or extending the library (for example, to support larger maps, add additional blocks/enemies, multiplayer support, etc), that should be fairly trivial to do. However, those works will be entirely separate from the clone and clearly distinguished. I think the original LaserTank is perfect the way it is. Remember, it has a special place in my heart (aww) and I would like to preserve the game as I remember it back then. --- Alright; now that we have a great deal of unnecessary history out of the way, let's get into the implementation details for this commit (if you're reading this as a blog entry, see the first commit). This commit represents a proof-of-concept showing that the LTG files (containing the LaserTank graphics) can be properly loaded and their masks properly applied. This was the first major concern for the project and, if a workaround were needed, would have prevented me from creating a full clone (as it would not support loading LTG files without having them first sent to the server, processed, and returned in a different format). The LTG file contains some metadata (including the name, author and description) as well as two bitmaps (BMPs) -- the game tileset and the associated mask. The game bitmap's position was static, but its length and the offset of the mask bitmap were provided by the four bytes immediately preceding the game bitmap (the TLTGREC struct in LTANK.H of the original sources represented this value as a DWORD, which represents a 32-bit integer). The only challenge with converting this value into an integer that we could use is its endianness -- is the most significant byte at the beginning or end? Windows programs (of which LaserTank is) generally write in little-endian format, but to be sure we can simply open up the LTG file in your favorite HEX editor (I simply use `xxd`). In the case of the original tileset, the four bytes immediately preceding the bitmap header (as identified by 'BM', or `424d`) were `7a f5 00 00`, which on its own clearly indicates little-endianness. We can verify by searching for 'BM' once again, and finding that it begins at location `f5 7a` (if your HEX editor displays in big-endian format). To convert into a number, we can simply add up each byte individually, left-shifting by 8N bits, where N is the 0-indexed byte position. Loading the BMP files was then fairly trivial; the file could be loaded into memory (read from disk using FileReader) and we could cut the relevant portions of the binary string out. The bitmaps could then be base64-encoded and the "src" attribute of an Image object set to 'data:image/bmp;base64,B', where B is the base64-encoded BMP. This could then be rendered however we please - CSS sprites or to a canvas. The problem with CSS sprites is that we need to apply the mask and there is no reliable way to do this without a canvas; transparency in the browser is normally handled using GIFs or PNGs. As it turns out, the canvas performs masking using the alpha channel as well, so I would have to create my own masking algorithm to manipulate the alpha channel of the tileset. To complicate matters even more, certain tiles had no mask, and they did not consistently represent the mask with all black (black is used in LT to indicate opacity), meaning that the algorithm would have to understand what tiles should be skipped entirely. The solution was to simply loop through each tile and set the alpha byte of each pixel relative to the respective pixel on the map. Because the images were created out of data in memory, the canvas is not tainted when the image is rendered before using getImageData(). To help speed up the process, since we know that the mask can only contain black and white, we need only check one of the channels; we do not need to calculate brightness. This process successfully returns each individual tile, properly masked, which can be rendered to the canvas using putImageData(). Crisis averted. With that major concern out of the way, the clone should no longer be a problem. I go into more detail in the comments within LtgLoader and TileMasker. This is going to be an exciting process, both because of the LT clone itself and because this is my first experience working with the canvas element. As aforementioned, I seldom create games and I have had no use for the canvas thus far. Hopefully this project will be well-received by both those who have played LaserTank in the past and the original developer of the game (Jim Kindley, JEK Software). LaserTank is a fairly old game and is not likely to be well known anymore, but the game itself is a blast (no pun intended) and bringing it to the browser, where it can be used on any platform (including mobile devices), should allow everyone to enjoy it. The source code is released under the GNU AGPL to ensure that the users' freedoms are preserved even if this game is rendered or in any way run server-side.
2012-03-11 21:38:06 -04:00
} );
}
} );