├── README.md └── src ├── Constants.js ├── Network.js ├── Scene.js ├── SceneFactory.js ├── Sprite.js ├── SpriteFactory.js ├── Timer.js ├── Utils.js ├── colDetector.js └── jGen.js /README.md: -------------------------------------------------------------------------------- 1 | # 💜If you like my projects, you can support me. 2 | 3 | | Coin/Symbol | Network | Adress | 4 | |------|---------|--------| 5 | | Bitcoin (BTC) | BTC | 1LU7DtLiKkWe3aAXbhRhNAMUFdrapWuAHW | 6 | | Tether (USDT) | TRC20 | TK7f7TXozWVbkHxdArAJd2rELu725q1Ac5 | 7 | | Tether (USDT) | TON | UQDI4e7xm_B7O_REaYd5CoyEz1Ki08t0EPlUim022_K9B2xa | 8 | 9 | 10 | jGen - JavaScript Game Engine 11 | ==== 12 | 13 | **IMPORTANT NOTE** 14 | 15 | I've decided to restart this project, old code now is available in the "first-try" branch. All new code will be published in the master branch. 16 | Follow the updates! 17 | 18 | **INTRODUCTION** 19 | 20 | It's Just an attempt to recreate "old school" isometric techniques in the modern web browsers without using a canvas, with only JavaScript and HTML. 21 | Don't expect to see something amazing while checking the demos. Project is very young and my attention at this moment is concentrated on getting map editor tool done. However the engine itself (checkout tanks game demo) currently supports simple sprite animation, collisions, rendering an isometric map. *At this moment works only in Chrome and Safari*. If you are interested in this project, please contact me. I'm looking for contributors. 22 | 23 | **LOOKING FOR PARTICIPANTS:** 24 | 25 | Our project is very young, and we're looking for people who's interested in game development as well as cutting edge web technologies. So if you feel like you can contribute, please let me know. You are not necessary have to be a developer, we need any help, such as graphic design / testing / bringing new ideas / promoting us / writing articles and etc. 26 | 27 | **FOR THE DEVELOPERS:** 28 | 29 | You can find classes overview as well as examples in our wiki, just follow the structure on the left side of the page (in the sidebar): http://code.google.com/p/jgen/wiki/Overview?tm=6 30 | 31 | **ONLINE DEMOS:** 32 | 33 | * Current state of the trunk (api-test): 34 | * [Collisions - Demo](http://angrycoding.github.io/jgen/api-test/) 35 | * [2D Map with sprite animation - Demo](http://angrycoding.github.io/jgen/api-test/map.html) 36 | * [Sprite Animation - Demo](http://angrycoding.github.io/jgen/api-test/sprite-animation.html) 37 | * [Tanks game online demo](http://angrycoding.github.io/jgen/tanks/) 38 | 39 | **For all demos, use the arrow keys to control the game scene.** 40 | 41 | * [Map editor online demo](http://angrycoding.github.io/jgen/map-editor/) 42 | 43 | **IMPORTANT! KNOWN ISSUES:** 44 | 45 | * Editor and demos supported only by Safari and Chrome.* Future plans is to extend it on Firefox. 46 | * Because of the restrictions in Google Chrome (XMLHttpRequest object is not able to read local files), you might need to upload editor or game demos on your local server in order to see it. However it works perfectly in Safari. 47 | 48 | **COUPLE OF SCREENSHOTS:** 49 | 50 | Rendering 2D map screenshot: 51 | 52 | ![jGen - JavaScript Game Engine - Rendering 2D Map with sprite animation - Demo](http://angrycoding.github.com/jgen/screenshots/2dmap.png) 53 | 54 | Map editor screenshots: 55 | 56 | ![jGen - JavaScript Game Engine - jGen Map Editor](http://angrycoding.github.com/jgen/screenshots/editor.png) 57 | 58 | Sample map that can be created using Map editor tool: 59 | 60 | ![jGen - JavaScript Game Engine - jGen Map Editor](http://angrycoding.github.com/jgen/screenshots/map_editor_grid.png) 61 | 62 | Demo game: 63 | 64 | ![jGen - JavaScript Game Engine - Tanks Game Demo](http://angrycoding.github.com/jgen/screenshots/screenshot.png) 65 | -------------------------------------------------------------------------------- /src/Constants.js: -------------------------------------------------------------------------------- 1 | define({ 2 | ON_MOVE: 1, 3 | ON_ROTATE: 2, 4 | ON_PROPCHANGE: 3, 5 | ON_WALKSTART: 4, 6 | ON_WALKSTOP: 5, 7 | ON_COLLIDE: 6 8 | }); -------------------------------------------------------------------------------- /src/Network.js: -------------------------------------------------------------------------------- 1 | define(function() { 2 | 3 | var XMLHttpFactories = [ 4 | function () {return new XMLHttpRequest()}, 5 | function () {return new ActiveXObject('Msxml2.XMLHTTP')}, 6 | function () {return new ActiveXObject('Msxml3.XMLHTTP')}, 7 | function () {return new ActiveXObject('Microsoft.XMLHTTP')} 8 | ]; 9 | 10 | function createXMLHTTPObject() { 11 | var xmlhttp = false; 12 | for (var c = 0; c < XMLHttpFactories.length; c++) { 13 | try { 14 | xmlhttp = XMLHttpFactories[c](); 15 | } catch (e) { continue; } 16 | break; 17 | } 18 | return xmlhttp; 19 | } 20 | 21 | function doRequest(requestURI, success, fail) { 22 | var request = createXMLHTTPObject(); 23 | if (!request) return fail(); 24 | 25 | var postData = null; 26 | 27 | try { 28 | request.open('GET', requestURI, true); 29 | 30 | request.onreadystatechange = function() { 31 | if (request.readyState !== 4) return; 32 | var status = request.status; 33 | if (status > 399 && status < 600) return fail(); 34 | success(request.responseText); 35 | }; 36 | 37 | request.send(null); 38 | 39 | } catch (exception) { fail(); } 40 | } 41 | 42 | function removeCallback(callbackId) { 43 | delete window[callbackId]; 44 | var scriptElement = document.getElementById(callbackId); 45 | scriptElement.parentNode.removeChild(scriptElement); 46 | } 47 | 48 | function doJSONPRequest(requestURI, success, fail, requestProps) { 49 | 50 | var requestQuery = []; 51 | var requestURI = Utils.uri.parse(requestURI); 52 | var requestParams = Utils.uri.parseQuery(requestURI.query); 53 | var callbackId = Utils.uniqueId('callback'); 54 | requestParams.callback = callbackId; 55 | 56 | for (var paramName in requestParams) { 57 | if (requestParams.hasOwnProperty(paramName)) { 58 | requestQuery.push(paramName + 59 | '=' + requestParams[paramName] 60 | ); 61 | } 62 | } 63 | 64 | requestURI = ( 65 | (requestURI.scheme ? requestURI.scheme + '://' : '') + 66 | (requestURI.authority ? requestURI.authority : '') + 67 | (requestURI.path ? requestURI.path : '') + 68 | (requestQuery.length ? '?' + requestQuery.join('&') : '') + 69 | (requestURI.fragment ? '#' + requestURI.fragment : '') 70 | ); 71 | 72 | var cleanup = removeCallback.bind(this, callbackId); 73 | 74 | window[callbackId] = function(response) { 75 | cleanup(); 76 | success(response); 77 | }; 78 | 79 | 80 | var scriptElement = document.createElement('script'); 81 | scriptElement.setAttribute('id', callbackId); 82 | scriptElement.setAttribute('src', requestURI); 83 | scriptElement.setAttribute('type', 'text/javascript'); 84 | 85 | scriptElement.onerror = function() { 86 | cleanup(); 87 | fail(); 88 | }; 89 | 90 | // scriptElement.onerror = 91 | // scriptElement.onreadystatechange = 92 | // removeCallback.bind(this, callbackId); 93 | 94 | document.getElementsByTagName('head')[0].appendChild(scriptElement); 95 | } 96 | 97 | return function(requestURI, success, fail, requestProps, isJSONP) { 98 | if (!isJSONP) doRequest(requestURI, success, fail, requestProps); 99 | else doJSONPRequest(requestURI, success, fail, requestProps); 100 | }; 101 | 102 | }); -------------------------------------------------------------------------------- /src/Scene.js: -------------------------------------------------------------------------------- 1 | define([ 2 | './Utils', 3 | './Constants', 4 | ], function(Utils, Constants) { 5 | 6 | return function( 7 | mapWidth, mapHeight, 8 | tileWidth, tileHeight, 9 | flatData, layersCount, 10 | 11 | target, 12 | viewPortWidth, viewPortHeight, 13 | scaleToFit 14 | ) { 15 | 16 | 17 | var locations = {}; 18 | 19 | 20 | var instanceID = Utils.uniqueId(); 21 | 22 | var viewPort = document.createElement('div'); 23 | viewPort.style.position = 'absolute'; 24 | 25 | if (scaleToFit) { 26 | 27 | var scaleX = (viewPortWidth / (tileWidth * mapWidth)); 28 | var scaleY = (viewPortHeight / (tileHeight * mapHeight)); 29 | 30 | 31 | viewPortWidth = tileWidth * mapWidth; 32 | viewPortHeight = tileHeight * mapHeight; 33 | 34 | 35 | viewPort.style.webkitTransformOrigin = '0px 0px'; 36 | viewPort.style.webkitTransform = 'scale(' + scaleX + ',' + scaleY + ')'; 37 | 38 | viewPort.style.transformOrigin = '0px 0px'; 39 | viewPort.style.transform = 'scale(' + scaleX + ',' + scaleY + ')'; 40 | } 41 | 42 | 43 | 44 | viewPort.style.width = viewPortWidth + 'px'; 45 | viewPort.style.height = viewPortHeight + 'px'; 46 | viewPort.style.overflow = 'hidden'; 47 | viewPort.className = 'jgen-viewport'; 48 | 49 | var tileViewPort = document.createElement('div'); 50 | tileViewPort.id = ('jgen-tiles-' + instanceID); 51 | tileViewPort.style.position = 'absolute'; 52 | viewPort.appendChild(tileViewPort); 53 | 54 | 55 | 56 | 57 | var tileElement = document.createElement('div'); 58 | tileElement.style.width = tileWidth + 'px'; 59 | tileElement.style.height = tileHeight + 'px'; 60 | 61 | var spriteViewPort = document.createElement('div'); 62 | spriteViewPort.style.position = 'absolute'; 63 | viewPort.appendChild(spriteViewPort); 64 | 65 | target.appendChild(viewPort); 66 | 67 | 68 | 69 | var spriteRefs = [], spritesCount = 0; 70 | var tileCache = {}, mapArea = mapWidth * mapHeight; 71 | var scrollLeft, scrollTop, scrollRight, scrollBottom; 72 | var marginLeft, marginTop, tileX, tileY, tileW, tileH; 73 | var maxScrollLeft = Math.max(0, tileWidth * mapWidth - viewPortWidth); 74 | var maxScrollTop = Math.max(0, tileHeight * mapHeight - viewPortHeight); 75 | 76 | function isSpriteOnScreen(sprite) { 77 | var spriteBound = sprite.bound(); 78 | return !( 79 | spriteBound[0] > scrollRight || 80 | spriteBound[1] > scrollBottom || 81 | spriteBound[2] < scrollLeft || 82 | spriteBound[3] < scrollTop 83 | ); 84 | } 85 | 86 | function translate(left, top) { 87 | 88 | scrollLeft = Math.max(0, Math.min(left, maxScrollLeft)); 89 | scrollTop = Math.max(0, Math.min(top, maxScrollTop)); 90 | scrollRight = scrollLeft + viewPortWidth; 91 | scrollBottom = scrollTop + viewPortHeight; 92 | 93 | tileX = Math.floor(scrollLeft / tileWidth); 94 | tileY = Math.floor(scrollTop / tileHeight); 95 | marginLeft = (scrollLeft % tileWidth); 96 | marginTop = (scrollTop % tileHeight); 97 | tileW = Math.ceil((viewPortWidth + marginLeft) / tileWidth); 98 | tileH = Math.ceil((viewPortHeight + marginTop) / tileHeight); 99 | 100 | for (var spriteRef, c = 0; c < spritesCount; c++) { 101 | spriteRef = spriteRefs[c]; 102 | // if (isSpriteOnScreen(spriteRef = spriteRefs[c])) { 103 | spriteRef.translate(scrollLeft, scrollTop); 104 | // if (!spriteRef.attached) { 105 | // spriteRef.attached = true; 106 | // spriteViewPort.appendChild(spriteRef.view()); 107 | // } 108 | // } else if (spriteRef.attached) { 109 | // spriteRef.attached = false; 110 | // spriteViewPort.removeChild(spriteRef.view()); 111 | // } 112 | } 113 | 114 | } 115 | 116 | function render() { 117 | 118 | var layerOffset, rowOffset1, rowOffset2; 119 | var layer, row, col, tile, tileID, cacheKey; 120 | 121 | var region; 122 | 123 | tileViewPort.style.left = -marginLeft + 'px'; 124 | tileViewPort.style.top = -marginTop + 'px'; 125 | 126 | for (layer = 0; layer < layersCount; layer++) { 127 | layerOffset = layer * mapArea; 128 | for (row = 0; row < tileH; row++) { 129 | rowOffset1 = rowOffset2 = layerOffset; 130 | rowOffset1 += (tileY + row) * mapHeight + tileX; 131 | rowOffset2 += row * mapHeight; 132 | for (col = 0; col < tileW; col++) { 133 | tileID = flatData[rowOffset1 + col]; 134 | if (tile = tileCache[cacheKey = rowOffset2 + col]) { 135 | if (tile[1] !== tileID) { 136 | tile[0].className = tile[1] = tileID; 137 | } 138 | } else { 139 | tile = tileElement.cloneNode(false); 140 | tile.style.zIndex = (layer * 2); 141 | tile.className = tileID; 142 | tile.style.left = (tileWidth * col) + 'px'; 143 | tile.style.top = (tileHeight * row) + 'px'; 144 | tileViewPort.appendChild(tile); 145 | tileCache[cacheKey] = [tile, tileID]; 146 | } 147 | } 148 | } 149 | } 150 | 151 | for (var spriteRef, c = 0; c < spritesCount; c++) { 152 | spriteRef = spriteRefs[c]; 153 | // if (spriteRef.attached) { 154 | spriteRef.render(); 155 | // } 156 | } 157 | 158 | } 159 | 160 | function onSpriteMove(sprite, ox, oy, nx, ny) { 161 | 162 | var w = sprite.width(); 163 | var h = sprite.height(); 164 | var sIndex = sprite.getProperty('index'); 165 | 166 | var fromX = Math.floor(ox / tileWidth); 167 | var fromY = Math.floor(oy / tileHeight); 168 | var toX = Math.floor((ox + w) / tileWidth); 169 | var toY = Math.floor((oy + h) / tileHeight); 170 | 171 | for (var y = fromY; y <= toY; y++) { 172 | for (var x = fromX; x <= toX; x++) { 173 | var cIndex = (y * mapWidth + x); 174 | if (!locations[cIndex]) continue; 175 | delete locations[cIndex][sIndex]; 176 | } 177 | } 178 | 179 | fromX = Math.floor(nx / tileWidth); 180 | fromY = Math.floor(ny / tileHeight); 181 | toX = Math.floor((nx + w) / tileWidth); 182 | toY = Math.floor((ny + h) / tileHeight); 183 | 184 | 185 | var collidesWith = {}; 186 | 187 | for (var y = fromY; y <= toY; y++) { 188 | for (var x = fromX; x <= toX; x++) { 189 | var cIndex = (y * mapWidth + x); 190 | 191 | // if (cIndex === 122) return false; 192 | if (!locations[cIndex]) locations[cIndex] = {}; 193 | for (var sIndex2 in locations[cIndex]) { 194 | if (sIndex !== sIndex2) { 195 | collidesWith[sIndex2] = true; 196 | } 197 | } 198 | locations[cIndex][sIndex] = true; 199 | } 200 | } 201 | 202 | 203 | for (var sIndex2 in collidesWith) { 204 | var f = spriteRefs[sIndex2].center(); 205 | var distance = Math.sqrt( 206 | Math.pow(f[0] - (nx + w / 2), 2) + 207 | Math.pow(f[1] - (ny + h / 2), 2) 208 | ); 209 | if (distance < 54) { 210 | console.info('COLLISION:', distance); 211 | return false; 212 | } 213 | } 214 | 215 | 216 | 217 | 218 | return true; 219 | } 220 | 221 | function addSprite(sprite) { 222 | 223 | spritesCount = spriteRefs.push(sprite); 224 | 225 | var spriteView = sprite.view(); 226 | spriteViewPort.appendChild(spriteView); 227 | 228 | sprite.setProperty('index', String(spritesCount - 1)); 229 | sprite.addEventListener(Constants.ON_MOVE, onSpriteMove); 230 | 231 | 232 | onSpriteMove( 233 | sprite, 234 | sprite.left(), 235 | sprite.top(), 236 | sprite.left(), 237 | sprite.top() 238 | ); 239 | 240 | } 241 | 242 | function addSprites() { 243 | var sprites = Array.prototype.slice.call(arguments); 244 | for (var c = 0; c < sprites.length; c++) { 245 | var sprite = sprites[c]; 246 | if (sprite instanceof Array) { 247 | addSprites.apply(this, sprite); 248 | } else addSprite(sprite); 249 | } 250 | } 251 | 252 | translate(0, 0); 253 | 254 | return { 255 | addSprite: addSprites, 256 | translate: translate, 257 | render: render 258 | }; 259 | 260 | }; 261 | 262 | }); -------------------------------------------------------------------------------- /src/SceneFactory.js: -------------------------------------------------------------------------------- 1 | define([ 2 | './Utils', 3 | './Network', 4 | './Constants', 5 | './Scene' 6 | ], function(Utils, Network, Constants, Scene) { 7 | 8 | 9 | function SceneFactory(definition, baseURI) { 10 | 11 | var mapWidth = definition.width; 12 | var mapHeight = definition.height; 13 | var tileWidth = definition.tilewidth; 14 | var tileHeight = definition.tileheight; 15 | var tileSet = definition.tileset; 16 | 17 | var AAA = [0]; 18 | 19 | for (var c = 0; c < tileSet.length; c++) { 20 | 21 | var tileDef = tileSet[c]; 22 | var tileSource = tileDef.source; 23 | 24 | if (Utils.isString(baseURI)) 25 | tileSource = Utils.resolveURI(tileSource, baseURI); 26 | 27 | AAA.push({ 28 | position: 'absolute', 29 | backgroundRepeat: 'no-repeat', 30 | backgroundImage: tileSource, 31 | backgroundPosition: [ 32 | -(tileDef.x || 0), 33 | -(tileDef.y || 0) 34 | ] 35 | }); 36 | 37 | } 38 | 39 | Utils.generateStyle('FACTORY_ID', AAA, 'tile-'); 40 | 41 | 42 | var flatData = []; 43 | var layers = definition.layer; 44 | 45 | for (var lIndex = 0; lIndex < layers.length; lIndex++) { 46 | for (var rIndex = 0; rIndex < mapHeight; rIndex++) { 47 | for (var cIndex = 0; cIndex < mapWidth; cIndex++) { 48 | var tileIndex = layers[lIndex][rIndex][cIndex]; 49 | if (!tileIndex) flatData.push(0); 50 | else flatData.push('tile-' + tileIndex); 51 | } 52 | } 53 | } 54 | 55 | return Scene.bind( 56 | this, 57 | definition.width, definition.height, 58 | definition.tilewidth, definition.tileheight, 59 | flatData, layers.length 60 | ); 61 | 62 | } 63 | 64 | SceneFactory.krang = function(baseURI, resourceURI, load) { 65 | var resourceURI = Utils.resolveURI(resourceURI, baseURI); 66 | Network(resourceURI, function(mapData) { 67 | try { 68 | mapData = JSON.parse(mapData); 69 | load(new SceneFactory(mapData, resourceURI)); 70 | } catch (exception) { 71 | alert(['ERROR LOADING SCENE:', resourceURI, exception]); 72 | } 73 | }, function() { 74 | alert(['ERROR LOADING SCENE:', resourceURI]); 75 | }); 76 | }; 77 | 78 | return SceneFactory; 79 | }); -------------------------------------------------------------------------------- /src/Sprite.js: -------------------------------------------------------------------------------- 1 | define([ 2 | './Constants' 3 | ], function(Constants) { 4 | 5 | var myApplyCache = {}; 6 | 7 | function Sprite(spriteView, definition, factoryHandlers) { 8 | 9 | var frameNumber = 0; 10 | var frameBuffer = []; 11 | var instanceHandlers = {}; 12 | 13 | var spriteLeft = 0, spriteTop = 0; 14 | var spriteWidth = definition.width; 15 | var spriteHeight = definition.height; 16 | var spriteFrames = definition.frames; 17 | var translateLeft = 0, translateTop = 0; 18 | 19 | var spriteProps = {}; 20 | var spriteOpacity = 1; 21 | var spriteFrame = null; 22 | var spriteRotation = null; 23 | var spriteVisible = false; 24 | 25 | var updateOpacity = false; 26 | var updatePosition = false; 27 | var updateVisibility = false; 28 | var oldWalkState = Constants.ON_WALKSTOP; 29 | var newWalkState = Constants.ON_WALKSTOP; 30 | 31 | spriteView = spriteView.cloneNode(true); 32 | 33 | function handleEvent(id) { 34 | if (typeof id !== 'number') return; 35 | 36 | var args = [], length = arguments.length - 1; 37 | for (var c = 1; c <= length; c++) args.push(arguments[c]); 38 | 39 | var caller = myApplyCache[length]; 40 | 41 | if (!caller) { 42 | var body = 'return c('; 43 | for (var c = 0; c < length; c++) { 44 | if (c) body += ','; 45 | body += 'a[' + c + ']'; 46 | } 47 | body += ')'; 48 | caller = new Function('c,a', body); 49 | myApplyCache[length] = caller; 50 | } 51 | 52 | var handlers = factoryHandlers[id]; 53 | if (typeof handlers !== 'undefined' && 54 | handlers.apply(this, args) === false) { 55 | return false; 56 | } 57 | 58 | handlers = instanceHandlers[id]; 59 | if (typeof handlers !== 'undefined') { 60 | length = handlers.length; 61 | for (var c = 0; c < length; c++) { 62 | if (caller(handlers[c], args) === false) { 63 | return false; 64 | } 65 | } 66 | } 67 | 68 | } 69 | 70 | function view() { 71 | return spriteView; 72 | } 73 | 74 | function left() { 75 | return spriteLeft; 76 | } 77 | 78 | function right() { 79 | return spriteLeft + spriteWidth; 80 | } 81 | 82 | function top() { 83 | return spriteTop; 84 | } 85 | 86 | function bottom() { 87 | return spriteTop + spriteHeight; 88 | } 89 | 90 | function position() { 91 | return [spriteLeft, spriteTop]; 92 | } 93 | 94 | function center() { 95 | return [ 96 | spriteLeft + spriteWidth / 2, 97 | spriteTop + spriteHeight / 2 98 | ]; 99 | } 100 | 101 | function width() { 102 | return spriteWidth; 103 | } 104 | 105 | function height() { 106 | return spriteHeight; 107 | } 108 | 109 | function size() { 110 | return [spriteWidth, spriteHeight]; 111 | } 112 | 113 | function rect() { 114 | return [ 115 | spriteLeft, spriteTop, 116 | spriteWidth, spriteHeight 117 | ]; 118 | } 119 | 120 | function bound() { 121 | return [ 122 | spriteLeft, spriteTop, 123 | spriteLeft + spriteWidth, 124 | spriteTop + spriteHeight 125 | ]; 126 | } 127 | 128 | function alpha(opacity) { 129 | if (spriteOpacity !== opacity) { 130 | spriteOpacity = opacity; 131 | updateOpacity = true; 132 | } 133 | } 134 | 135 | function show() { 136 | if (!spriteVisible) { 137 | spriteVisible = true; 138 | updateVisibility = true; 139 | } 140 | } 141 | 142 | function hide() { 143 | if (spriteVisible) { 144 | spriteVisible = false; 145 | updateVisibility = true; 146 | } 147 | } 148 | 149 | function visible() { 150 | return spriteVisible; 151 | } 152 | 153 | function rotation() { 154 | return spriteRotation; 155 | } 156 | 157 | function translate(left, top) { 158 | if (translateLeft !== left || 159 | translateTop !== top) { 160 | translateLeft = left; 161 | translateTop = top; 162 | updatePosition = true; 163 | } 164 | } 165 | 166 | function rotate(rotation) { 167 | if (spriteRotation === rotation || handleEvent( 168 | Constants.ON_ROTATE, this, rotation) !== false) { 169 | spriteRotation = rotation; 170 | return true; 171 | } return false; 172 | } 173 | 174 | function move(left, top) { 175 | if (spriteLeft !== left || 176 | spriteTop !== top) { 177 | 178 | if (handleEvent( 179 | Constants.ON_MOVE, this, 180 | spriteLeft, spriteTop, 181 | left, top 182 | ) !== false) { 183 | 184 | spriteLeft = left; 185 | spriteTop = top; 186 | updatePosition = true; 187 | 188 | return true; 189 | 190 | } 191 | 192 | return false; 193 | 194 | } 195 | return true; 196 | } 197 | 198 | function walk(angle, speed) { 199 | if (this.rotate(angle) && this.move( 200 | spriteLeft + Math.cos(angle) * speed, 201 | spriteTop + Math.sin(angle) * speed)) { 202 | newWalkState = Constants.ON_WALKSTART; 203 | return true; 204 | } return false; 205 | } 206 | 207 | function getProperty(name) { 208 | return spriteProps[name]; 209 | } 210 | 211 | function setProperty(name, value) { 212 | if (spriteProps[name] !== value) { 213 | spriteProps[name] = value; 214 | handleEvent(Constants.ON_PROPCHANGE, this, spriteProps); 215 | } 216 | } 217 | 218 | function setFrame(frame) { 219 | if (spriteFrame !== frame) { 220 | frameNumber = 0; 221 | frameBuffer = spriteFrames[ 222 | spriteFrame = frame 223 | ]; 224 | } 225 | } 226 | 227 | function addEventListener(id, handler) { 228 | if (typeof id === 'number' && 229 | handler instanceof Function) { 230 | if (!instanceHandlers[id]) instanceHandlers[id] = []; 231 | instanceHandlers[id].push(handler); 232 | } 233 | } 234 | 235 | function render() { 236 | 237 | if (oldWalkState !== newWalkState) { 238 | handleEvent(newWalkState, this); 239 | oldWalkState = newWalkState; 240 | } else newWalkState = Constants.ON_WALKSTOP; 241 | 242 | if (updateVisibility) { 243 | updateVisibility = false; 244 | if (spriteVisible) { 245 | spriteView.style.display = 'block'; 246 | } else { 247 | spriteView.style.display = 'none'; 248 | } 249 | } 250 | 251 | if (updateOpacity) { 252 | updateOpacity = false; 253 | spriteView.style.opacity = spriteOpacity; 254 | } 255 | 256 | if (updatePosition) { 257 | updatePosition = false; 258 | spriteView.style.left = (spriteLeft - translateLeft) + 'px'; 259 | spriteView.style.top = (spriteTop - translateTop) + 'px'; 260 | } 261 | 262 | if (frameBuffer.length) { 263 | spriteView.style.backgroundPosition = ('-' + ( 264 | frameBuffer[Math.ceil(frameNumber)] || 265 | frameBuffer[frameNumber = 0] 266 | ).join('px -') + 'px'); 267 | frameNumber += 1; 268 | } 269 | 270 | } 271 | 272 | return { 273 | view: view, 274 | left: left, 275 | right: right, 276 | top: top, 277 | bottom: bottom, 278 | position: position, 279 | center: center, 280 | width: width, 281 | height: height, 282 | size: size, 283 | rect: rect, 284 | bound: bound, 285 | visible: visible, 286 | rotation: rotation, 287 | 288 | move: move, 289 | show: show, 290 | hide: hide, 291 | walk: walk, 292 | alpha: alpha, 293 | rotate: rotate, 294 | render: render, 295 | setFrame: setFrame, 296 | translate: translate, 297 | getProperty: getProperty, 298 | setProperty: setProperty, 299 | addEventListener: addEventListener, 300 | 301 | // addSprite: addSprite 302 | }; 303 | 304 | } 305 | 306 | return Sprite; 307 | 308 | }); -------------------------------------------------------------------------------- /src/SpriteFactory.js: -------------------------------------------------------------------------------- 1 | define([ 2 | './Utils', 3 | './Network', 4 | './Sprite' 5 | ], function(Utils, Network, Sprite) { 6 | 7 | function SpriteFactoryException(spriteURI, message) { 8 | var msg = ('error loading sprite "' + spriteURI); 9 | if (message) msg += ('" with message "' + message + '"'); 10 | return { 11 | file: spriteURI, 12 | message: message, 13 | toString: function() { return msg; } 14 | }; 15 | } 16 | 17 | function createSpriteFactoryView(factoryID, cssProps) { 18 | var factoryCSS = {}; 19 | var factoryClass = Utils.uniqueId('sprite-'); 20 | factoryCSS[factoryClass] = cssProps; 21 | Utils.generateStyle(factoryID, factoryCSS); 22 | var factoryView = document.createElement('div'); 23 | factoryView.className = factoryClass; 24 | return factoryView; 25 | } 26 | 27 | function SpriteFactory(definition, baseURI) { 28 | 29 | if (!Utils.isMap(definition)) { 30 | throw new SpriteFactoryException( 31 | baseURI, 'definition is not a map' 32 | ); 33 | } 34 | 35 | var factoryHandlers = {}; 36 | 37 | var spriteWidth = definition.width; 38 | var spriteHeight = definition.height; 39 | var spriteSource = definition.source; 40 | var spriteFrames = definition.frames; 41 | 42 | if (Utils.isString(baseURI)) 43 | spriteSource = Utils.resolveURI(spriteSource, baseURI); 44 | 45 | var factoryID = Utils.uniqueId('jgen-sf-'); 46 | 47 | var factoryView = createSpriteFactoryView(factoryID, { 48 | display: 'none', 49 | position: 'absolute', 50 | overflow: 'hidden', 51 | width: spriteWidth, 52 | height: spriteHeight, 53 | backgroundColor: 'red', 54 | backgroundRepeat: 'no-repeat', 55 | backgroundPosition: [0, 0], 56 | backgroundImage: spriteSource 57 | }); 58 | 59 | var Constructor = Sprite.bind( 60 | this, factoryView, 61 | definition, factoryHandlers 62 | ); 63 | 64 | Constructor.event = function(id, handler) { 65 | if (typeof id !== 'number') return; 66 | if (handler instanceof Function) { 67 | factoryHandlers[id] = handler; 68 | } else delete factoryHandlers[id]; 69 | }; 70 | 71 | return Constructor; 72 | 73 | } 74 | 75 | SpriteFactory.krang = function(baseURI, spriteURI, load) { 76 | var spriteURI = Utils.resolveURI(spriteURI, baseURI); 77 | Network(spriteURI, function(spriteData) { try { 78 | spriteData = JSON.parse(spriteData); 79 | load(new SpriteFactory(spriteData, spriteURI)); 80 | } catch (exception) { 81 | throw new SpriteFactoryException(spriteURI, exception.message); 82 | }}, function() { throw new SpriteFactoryException(spriteURI); }); 83 | }; 84 | 85 | return SpriteFactory; 86 | 87 | }); -------------------------------------------------------------------------------- /src/Timer.js: -------------------------------------------------------------------------------- 1 | define(function() { 2 | 3 | var lastTime = 0; 4 | var vendors = ['ms', 'moz', 'webkit', 'o']; 5 | 6 | for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { 7 | window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame']; 8 | window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] 9 | || window[vendors[x]+'CancelRequestAnimationFrame']; 10 | } 11 | 12 | if (!window.requestAnimationFrame) { 13 | window.requestAnimationFrame = function(callback, element) { 14 | var currTime = new Date().getTime(); 15 | var timeToCall = Math.max(0, 16 - (currTime - lastTime)); 16 | var id = window.setTimeout(function() { 17 | callback(currTime + timeToCall); 18 | }, timeToCall); 19 | lastTime = currTime + timeToCall; 20 | return id; 21 | }; 22 | } 23 | 24 | if (!window.cancelAnimationFrame) { 25 | window.cancelAnimationFrame = function(id) { 26 | clearTimeout(id); 27 | }; 28 | } 29 | 30 | return function(callback, delay) { 31 | var time; 32 | (function draw() { 33 | requestAnimationFrame(draw); 34 | var now = new Date().getTime(), 35 | dt = now - (time || now); 36 | time = now; 37 | callback(dt); 38 | })(); 39 | }; 40 | 41 | }); -------------------------------------------------------------------------------- /src/Utils.js: -------------------------------------------------------------------------------- 1 | define(function() { 2 | 3 | var DEG_TO_RAD = (180 / Math.PI); 4 | 5 | var CCASE_DASH = /([A-Z])/g; 6 | var URL_DIRNAME_REGEXP = /^(.*)\//; 7 | var URL_PARSER_REGEXP = /^(?:([^:\/?\#]+):)?(?:\/\/([^\/?\#]*))?([^?\#]*)(?:\?([^\#]*))?(?:\#(.*))?/; 8 | var CSS_URL_REGEXP = /^\s*url\(.*\)\s*$/i; 9 | 10 | var VENDOR_PREFIXES = { 11 | 'webkit': true, 12 | 'moz': true, 13 | 'ms': true, 14 | 'o': true 15 | }; 16 | 17 | var CSS_NUM_PROPS = { 18 | 'column-count': true, 19 | 'fill-opacity': true, 20 | 'font-weight': true, 21 | 'line-height': true, 22 | 'opacity': true, 23 | 'orphans': true, 24 | 'widows': true, 25 | 'z-index': true, 26 | 'zoom': true 27 | }; 28 | 29 | var CSS_ARR_PROPS = { 30 | 'background-position': true, 31 | 'background-size': true 32 | }; 33 | 34 | var last_generated_uid = 1; 35 | 36 | function cCaseToDash(value) { 37 | if (!isString(value)) return ''; 38 | return value.replace(CCASE_DASH, function(all, letter) { 39 | return ('-' + letter.toLowerCase()); 40 | }); 41 | } 42 | 43 | function forEach(collection, callback) { 44 | var key; 45 | if (isArray(collection)) { 46 | for (key = 0; key < collection.length; key++) { 47 | callback(collection[key], key); 48 | } 49 | } else if (isMap(collection)) { 50 | for (key in collection) { 51 | callback(collection[key], key); 52 | } 53 | } 54 | } 55 | 56 | function removeDotSegments(path) { 57 | var path = path.split('/'); 58 | var isAbsolute = (path[0] === ''); 59 | var result = [], fragment = ''; 60 | if (isAbsolute) path.shift(); 61 | while (path.length) { 62 | fragment = path.shift(); 63 | if (fragment === '..') { 64 | result.pop(); 65 | } else if (fragment !== '.') { 66 | result.push(fragment); 67 | } 68 | } 69 | if (isAbsolute) result.unshift(''); 70 | if (fragment === '.' || fragment === '..') result.push(''); 71 | return result.join('/'); 72 | } 73 | 74 | function isNumber(value) { 75 | return (typeof(value) === 'number' && !isNaN(value)); 76 | } 77 | 78 | function isNumeric(value) { 79 | return (!isNaN(parseFloat(value)) && isFinite(value)); 80 | } 81 | 82 | function isString(value) { 83 | return (typeof(value) === 'string'); 84 | } 85 | 86 | function isArray(value) { 87 | return (value instanceof Array); 88 | } 89 | 90 | function isObject(value) { 91 | return (value instanceof Object); 92 | } 93 | 94 | function isMap(value) { 95 | return (value instanceof Object && 96 | !(value instanceof Array)); 97 | } 98 | 99 | function isDOMElement(value) { 100 | try { 101 | return value instanceof HTMLElement; 102 | } catch (exception) { return ( 103 | typeof(value) === 'object' && 104 | value.nodeType === 1 105 | ); } 106 | } 107 | 108 | function rad2deg(angle) { 109 | return (angle * DEG_TO_RAD); 110 | } 111 | 112 | function deg2rad(angle) { 113 | return (angle / DEG_TO_RAD); 114 | } 115 | 116 | function parseURI(relURI) { 117 | var result = relURI.match(URL_PARSER_REGEXP); 118 | return { 119 | scheme: (result[1] || ''), 120 | authority: (result[2] || ''), 121 | path: (result[3] || ''), 122 | query: (result[4] || ''), 123 | fragment: (result[5] || '') 124 | }; 125 | } 126 | 127 | function uniqueId(prefix) { 128 | if (!isString(prefix)) prefix = ''; 129 | return (prefix + (last_generated_uid++)); 130 | } 131 | 132 | function resolveURI(relURI, baseURI) { 133 | var relUri = parseURI(relURI); 134 | var baseUri = parseURI(baseURI); 135 | var res = '', ts = ''; 136 | if (relUri.scheme) { 137 | res += (relUri.scheme + ':'); 138 | if (ts = relUri.authority) res += ('//' + ts); 139 | if (ts = removeDotSegments(relUri.path)) res += ts; 140 | if (ts = relUri.query) res += ('?' + ts); 141 | } else { 142 | if (ts = baseUri.scheme) res += (ts + ':'); 143 | if (ts = relUri.authority) { 144 | res += ('//' + ts); 145 | if (ts = removeDotSegments(relUri.path || '')) res += ts; 146 | if (ts = relUri.query) res += ('?' + ts); 147 | } else { 148 | if (ts = baseUri.authority) res += ('//' + ts); 149 | if (ts = relUri.path) { 150 | if (ts = removeDotSegments(ts.charAt(0) === '/' ? ts : ( 151 | baseUri.authority && !baseUri.path ? '/' : 152 | (baseUri.path.match(URL_DIRNAME_REGEXP) || [''])[0] 153 | ) + ts)) res += ts; 154 | if (ts = relUri.query) res += ('?' + ts); 155 | } else { 156 | if (ts = baseUri.path) res += ts; 157 | if ((ts = relUri.query) || 158 | (ts = baseUri.query)) res += ('?' + ts); 159 | } 160 | } 161 | } 162 | if (ts = relUri.fragment) res += ('#' + ts); 163 | return res; 164 | } 165 | 166 | function generateCSS(targetEl, cssProps, namePrefix) { 167 | if (!isString(namePrefix)) namePrefix = ''; 168 | var cssData = [], className, sPrefix, vendorPrefix; 169 | forEach(cssProps, function(classProps, className) { 170 | var propName, propValue; 171 | sPrefix = className[0]; 172 | if (sPrefix === '#' || sPrefix === '.') 173 | className = className.substr(1); 174 | else sPrefix = '.'; 175 | cssData.push(sPrefix + namePrefix + className + '{'); 176 | if (isMap(classProps)) for (propName in classProps) { 177 | propValue = classProps[propName]; 178 | propName = cCaseToDash(propName); 179 | vendorPrefix = propName.split('-')[0]; 180 | if (VENDOR_PREFIXES[vendorPrefix] === true) 181 | propName = ('-' + propName); 182 | if (isNumeric(propValue) && 183 | CSS_NUM_PROPS[propName] !== true) { 184 | propValue += 'px'; 185 | } else if (isArray(propValue) && 186 | CSS_ARR_PROPS[propName] === true) { 187 | for (var c = 0; c < propValue.length; c++) { 188 | if (isNumeric(propValue[c])) 189 | propValue[c] += 'px'; 190 | } 191 | propValue = propValue.join(' '); 192 | } 193 | if (propName === 'background-image' && 194 | !CSS_URL_REGEXP.test(propValue)) { 195 | propValue = 'url("' + propValue + '")'; 196 | } 197 | if (isString(propValue) || isNumber(propValue)) 198 | cssData.push(propName + ':' + propValue + ';'); 199 | } 200 | cssData.push('}'); 201 | }); 202 | targetEl.innerHTML = cssData.join(''); 203 | } 204 | 205 | function generateStyle() { 206 | var styleElement, elementID, elementCSS, namePrefix; 207 | var args = Array.prototype.slice.call(arguments); 208 | if (isString(args[0])) { 209 | elementID = args.shift(); 210 | styleElement = document.getElementById(elementID); 211 | } else if (isDOMElement(args[0])) { 212 | styleElement = args.shift(); 213 | } 214 | if (isObject(args[0])) elementCSS = args.shift(); 215 | if (isString(args[0])) namePrefix = args.shift(); 216 | if (!styleElement) { 217 | styleElement = document.createElement('style'); 218 | styleElement.setAttribute('type', 'text/css'); 219 | if (elementID) styleElement.id = elementID; 220 | document.head.appendChild(styleElement); 221 | } 222 | generateCSS(styleElement, elementCSS, namePrefix); 223 | return styleElement; 224 | } 225 | 226 | return { 227 | isNumber: isNumber, 228 | isNumeric: isNumeric, 229 | isString: isString, 230 | isArray: isArray, 231 | isMap: isMap, 232 | rad2deg: rad2deg, 233 | deg2rad: deg2rad, 234 | uniqueId: uniqueId, 235 | resolveURI: resolveURI, 236 | generateCSS: generateCSS, 237 | generateStyle: generateStyle 238 | }; 239 | 240 | }); -------------------------------------------------------------------------------- /src/colDetector.js: -------------------------------------------------------------------------------- 1 | // var COL_DETECTOR = null; 2 | 3 | // function setPosition(x, y) { 4 | // var collision = cManager.adjust(x, y, spriteWidth, spriteHeight); 5 | 6 | // if (collision) { 7 | 8 | // if (instance.y + spriteHeight <= collision[1]) { 9 | // y = collision[1] - spriteHeight; 10 | // } else if (instance.y >= collision[1] + collision[3]) { 11 | // y = collision[1] + collision[3]; 12 | // } 13 | 14 | // if (instance.x + spriteWidth <= collision[0]) { 15 | // x = collision[0] - spriteWidth; 16 | // } else if (instance.x >= collision[0] + collision[2]) { 17 | // x = collision[0] + collision[2]; 18 | // } 19 | // } 20 | 21 | // var isMoved = (instance.x !== x || instance.y !== y); 22 | 23 | // instance.x = x; 24 | // instance.y = y; 25 | 26 | 27 | // return isMoved; 28 | // } 29 | 30 | -------------------------------------------------------------------------------- /src/jGen.js: -------------------------------------------------------------------------------- 1 | define([ 2 | './Utils', 3 | './Timer', 4 | './SceneFactory', 5 | './SpriteFactory', 6 | './Constants' 7 | ], function(Utils, Timer, SceneFactory, SpriteFactory, Constants) { 8 | 9 | function krang(baseURI, pluginURI, load) { 10 | var pluginURI = pluginURI.split('!'); 11 | var action = pluginURI.shift().toLowerCase(); 12 | pluginURI = pluginURI.join('!'); 13 | if (action === 'map') 14 | SceneFactory.krang(baseURI, pluginURI, load); 15 | else if (action === 'sprite') 16 | SpriteFactory.krang(baseURI, pluginURI, load); 17 | } 18 | 19 | return { 20 | krang: krang, 21 | Utils: Utils, 22 | Timer: Timer, 23 | Constants: Constants 24 | }; 25 | 26 | }); --------------------------------------------------------------------------------