├── font.png ├── tiles.png ├── screenshot.png ├── README.md ├── index.html ├── gif.worker.js ├── gameEngineDebug.js ├── LICENSE ├── gameEngine.js ├── CCapture.all.min.js └── game.js /font.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KilledByAPixel/EggTimeRewind/HEAD/font.png -------------------------------------------------------------------------------- /tiles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KilledByAPixel/EggTimeRewind/HEAD/tiles.png -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KilledByAPixel/EggTimeRewind/HEAD/screenshot.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Egg Time Rewind 2 | Crack a few space eggs in this low res Nokia throwback! 3 | 4 | Made in only 4 days for [Nokia 3310 Jam 2!](https://itch.io/jam/nokiajam2) 5 | 6 | # [LIVE DEMO!](https://killedbyapixel.github.io/EggTimeRewind) 7 | # [itch.io page](https://killedbyapixel.itch.io/egg-time) 8 | 9 | ![screenshot](/screenshot.png) 10 | 11 | ## Time REWINDS you die... but your LOST SOULS return! 12 | 13 | ### Controls 14 | * Move with wasd, num pad, mouse, or gamepad. 15 | * Shoot with space, shift, gamepad button or mouse click. 16 | * You can reset the game with R. 17 | * Game automatically pauses when focus is lost. 18 | 19 | ### How to Play 20 | * Each kill is worth 1 point. 21 | * Break the SPACE EGG for 50 points! 22 | * When you die, your previous lives will return to help. 23 | * Level is randomly generated for each new game. 24 | * The game will save your high score, good luck. 25 | 26 | ### Code Notes 27 | * This was reworked from my [js13k game Bounce Back.](https://github.com/KilledByAPixel/BounceBack) 28 | * Code is separated into game, engine, and debug modules. 29 | * There is a debug mode you can enable in code. 30 | * Number keys control debug mode features. 31 | * Ccapture is used to capture gifs. 32 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 20 | 21 | 22 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /gif.worker.js: -------------------------------------------------------------------------------- 1 | (function(b){function a(b,d){if({}.hasOwnProperty.call(a.cache,b))return a.cache[b];var e=a.resolve(b);if(!e)throw new Error('Failed to resolve module '+b);var c={id:b,require:a,filename:b,exports:{},loaded:!1,parent:d,children:[]};d&&d.children.push(c);var f=b.slice(0,b.lastIndexOf('/')+1);return a.cache[b]=c.exports,e.call(c.exports,c,c.exports,f,b),c.loaded=!0,a.cache[b]=c.exports}a.modules={},a.cache={},a.resolve=function(b){return{}.hasOwnProperty.call(a.modules,b)?a.modules[b]:void 0},a.define=function(b,c){a.modules[b]=c},a.define('/gif.worker.coffee',function(d,e,f,g){var b,c;b=a('/GIFEncoder.js',d),c=function(a){var c,e,d,f;return c=new b(a.width,a.height),a.index===0?c.writeHeader():c.firstFrame=!1,c.setTransparent(a.transparent),c.setRepeat(a.repeat),c.setDelay(a.delay),c.setQuality(a.quality),c.addFrame(a.data),a.last&&c.finish(),d=c.stream(),a.data=d.pages,a.cursor=d.cursor,a.pageSize=d.constructor.pageSize,a.canTransfer?(f=function(c){for(var b=0,d=a.data.length;b=c.pageSize&&this.newPage(),this.pages[this.page][this.cursor++]=a},c.prototype.writeUTFBytes=function(b){for(var c=b.length,a=0;a=0&&(this.dispose=a)},b.prototype.setRepeat=function(a){this.repeat=a},b.prototype.setTransparent=function(a){this.transparent=a},b.prototype.addFrame=function(a){this.image=a,this.getImagePixels(),this.analyzePixels(),this.firstFrame&&(this.writeLSD(),this.writePalette(),this.repeat>=0&&this.writeNetscapeExt()),this.writeGraphicCtrlExt(),this.writeImageDesc(),this.firstFrame||this.writePalette(),this.writePixels(),this.firstFrame=!1},b.prototype.finish=function(){this.out.writeByte(59)},b.prototype.setQuality=function(a){a<1&&(a=1),this.sample=a},b.prototype.writeHeader=function(){this.out.writeUTFBytes('GIF89a')},b.prototype.analyzePixels=function(){var g=this.pixels.length,d=g/3;this.indexedPixels=new Uint8Array(d);var a=new f(this.pixels,this.sample);a.buildColormap(),this.colorTab=a.getColormap();var b=0;for(var c=0;c>16,l=(e&65280)>>8,m=e&255,c=0,d=16777216,j=this.colorTab.length;for(var a=0;a=0&&(a=dispose&7),a<<=2,this.out.writeByte(0|a|0|b),this.writeShort(this.delay),this.out.writeByte(this.transIndex),this.out.writeByte(0)},b.prototype.writeImageDesc=function(){this.out.writeByte(44),this.writeShort(0),this.writeShort(0),this.writeShort(this.width),this.writeShort(this.height),this.firstFrame?this.out.writeByte(0):this.out.writeByte(128|this.palSize)},b.prototype.writeLSD=function(){this.writeShort(this.width),this.writeShort(this.height),this.out.writeByte(240|this.palSize),this.out.writeByte(0),this.out.writeByte(0)},b.prototype.writeNetscapeExt=function(){this.out.writeByte(33),this.out.writeByte(255),this.out.writeByte(11),this.out.writeUTFBytes('NETSCAPE2.0'),this.out.writeByte(3),this.out.writeByte(1),this.writeShort(this.repeat),this.out.writeByte(0)},b.prototype.writePalette=function(){this.out.writeBytes(this.colorTab);var b=768-this.colorTab.length;for(var a=0;a>8&255)},b.prototype.writePixels=function(){var a=new g(this.width,this.height,this.indexedPixels,this.colorDepth);a.encode(this.out)},b.prototype.stream=function(){return this.out},e.exports=b}),a.define('/LZWEncoder.js',function(e,g,h,i){function f(y,D,C,B){function w(a,b){r[f++]=a,f>=254&&t(b)}function x(b){u(a),k=i+2,j=!0,l(i,b)}function u(b){for(var a=0;a=0){y=w-d,d===0&&(y=1);do if((d-=y)<0&&(d+=w),h[d]===g){e=n[d];continue a}while(h[d]>=0)}l(e,r),e=t,k<1<0&&(a.writeByte(f),a.writeBytes(r,0,f),f=0)}function p(a){return(1<0?g|=a<=8)w(g&255,c),g>>=8,e-=8;if((k>m||j)&&(j?(m=p(n_bits=q),j=!1):(++n_bits,n_bits==b?m=1<0)w(g&255,c),g>>=8,e-=8;t(c)}}var s=Math.max(2,B),r=new Uint8Array(256),h=new Int32Array(a),n=new Int32Array(a),g,e=0,f,k=0,m,j=!1,q,i,o;this.encode=z}var c=-1,b=12,a=5003,d=[0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535];e.exports=f}),a.define('/TypedNeuQuant.js',function(A,F,E,D){function C(A,B){function I(){o=[],q=new Int32Array(256),t=new Int32Array(a),y=new Int32Array(a),z=new Int32Array(a>>3);var c,d;for(c=0;c>=b,o[c][1]>>=b,o[c][2]>>=b,o[c][3]=c}function K(b,a,c,e,f){o[a][0]-=b*(o[a][0]-c)/d,o[a][1]-=b*(o[a][1]-e)/d,o[a][2]-=b*(o[a][2]-f)/d}function L(j,e,n,l,k){var h=Math.abs(e-j),i=Math.min(e+j,a),g=e+1,f=e-1,m=1,b,d;while(gh)d=z[m++],gh&&(b=o[f--],b[0]-=d*(b[0]-n)/c,b[1]-=d*(b[1]-l)/c,b[2]-=d*(b[2]-k)/c)}function C(p,s,q){var h=2147483647,k=h,d=-1,m=d,c,j,e,n,l;for(c=0;c>i-b),n>g,y[c]-=l,t[c]+=l<>1,b=f+1;b>1,b=f+1;b<256;b++)q[b]=n}function E(j,i,k){var b,d,c,e=1e3,h=-1,f=q[i],g=f-1;while(f=0)f=e?f=a:(f++,c<0&&(c=-c),b=d[0]-j,b<0&&(b=-b),c+=b,c=0&&(d=o[g],c=i-d[1],c>=e?g=-1:(g--,c<0&&(c=-c),b=d[0]-j,b<0&&(b=-b),c+=b,c>h;for(a<=1&&(a=0),c=0;c=f&&(g-=f),c++,q===0&&(q=1),c%q===0)for(n-=n/D,o-=o/v,a=o>>h,a<=1&&(a=0),e=0;e>g,r=e<>3,h=6,t=1<DebugConsoleKeyDown(e) 81 | 82 | debugConsoleDisplayTextArea = document.createElement('textarea'); 83 | debugConsoleDisplayTextArea.style="height:100px;width:90%;display:none;color:#FFF;background-color:#000;" 84 | mainCanvas.before(debugConsoleDisplayTextArea); 85 | 86 | downloadLink = document.createElement('a'); 87 | downloadLink.display='none'; 88 | downloadLink.before(debugConsoleTextArea); 89 | 90 | if (debugCanvas) 91 | { 92 | document.body.style.overflow='visible'; 93 | document.body.style.background='#400' 94 | mainCanvas.style.border='2px solid #F00'; 95 | mainCanvas.style.width=mainCanvas.width+'px'; 96 | mainCanvas.style.height=mainCanvas.height+'px'; 97 | 98 | document.body.appendChild(levelCanvas); 99 | levelCanvas.style.border='2px solid #F00'; 100 | levelCanvas.style.width=(levelCanvas.width)+'px'; 101 | levelCanvas.style.height=(levelCanvas.height)+'px'; 102 | levelCanvas.style.display ='block'; 103 | 104 | document.body.appendChild(tileMaskCanvas); 105 | tileMaskCanvas.style.border='2px solid #F00'; 106 | tileMaskCanvas.style.width=(tileMaskCanvas.width)+'px'; 107 | tileMaskCanvas.style.height=(tileMaskCanvas.height)+'px'; 108 | tileMaskCanvas.style.display ='block'; 109 | } 110 | 111 | if (captureMode) 112 | { 113 | mainCanvasSize.Set(400,300); 114 | mainCanvas.style.width='50%'; 115 | mainCanvas.style.height='50%'; 116 | } 117 | } 118 | 119 | function DebugRect(pos,size,color="#F00") 120 | { 121 | if (debug) debugRects.push({pos:pos.Clone(),size:size.Clone(),color:color.slice(0)}); 122 | } 123 | 124 | function DebugPoint(pos,size=.1,color="#F00") 125 | { 126 | if (debug) DebugRect(pos, new Vector2(size,size), color) 127 | } 128 | 129 | function RenderDebugRects() 130 | { 131 | function RenderDebugRect(pos,size,color) 132 | { 133 | mainCanvasContext.lineWidth=1; 134 | size.Multiply(tileSize); 135 | let renderPos = pos.Clone(); 136 | renderPos.Subtract(cameraPos); 137 | renderPos.Multiply(tileSize); 138 | renderPos.Subtract(size); 139 | renderPos.Multiply(cameraScale); 140 | mainCanvasContext.strokeStyle=color; 141 | mainCanvasContext.save(); 142 | mainCanvasContext.strokeRect( 143 | renderPos.x + mainCanvas.width/2|0, 144 | renderPos.y + mainCanvas.height/2|0, 145 | 2*cameraScale*size.x|0, 2*cameraScale*size.y|0); 146 | mainCanvasContext.restore(); 147 | } 148 | 149 | for( let d of debugRects ) 150 | RenderDebugRect(d.pos,d.size,d.color); 151 | debugRects = []; 152 | } 153 | 154 | let captureCanvas = 0; 155 | let captureCanvasContext = 0; 156 | function UpdateDebug() 157 | { 158 | if (capturer) 159 | { 160 | if (!captureCanvas) 161 | { 162 | // create level canvas to cache level image and groun effects 163 | captureCanvas = document.createElement('canvas'); 164 | captureCanvasContext = captureCanvas.getContext('2d'); 165 | captureCanvas.display='none'; 166 | 167 | let captureScale = 8; 168 | captureCanvas.width = mainCanvas.width*captureScale; 169 | captureCanvas.height = mainCanvas.height*captureScale; 170 | } 171 | 172 | captureCanvas.width |= 0; 173 | captureCanvasContext.imageSmoothingEnabled = 0; 174 | 175 | // clear canvas so transition starts on a blank screen 176 | captureCanvasContext.fillStyle='#43523d'; 177 | captureCanvasContext.fillRect(0,0,captureCanvas.width, captureCanvas.height); 178 | 179 | captureCanvasContext.drawImage(mainCanvas,0,0, 180 | mainCanvas.width,mainCanvas.height,0,0,captureCanvas.width,captureCanvas.height); 181 | 182 | capturer.capture( captureCanvas ); 183 | } 184 | 185 | ++debugFrame; 186 | UpdateDebugControls(); 187 | 188 | if (debugCollision) 189 | { 190 | let s = tileSize*level.size; 191 | DebugRect(new Vector2(s/2,s/2), new Vector2(s/2,s/2),'#F00'); 192 | } 193 | 194 | debugConsoleTextArea.style.display=debugConsole?'block':'none'; 195 | debugConsoleDisplayTextArea.style.display=debugConsole?'block':'none'; 196 | 197 | if (!debug) 198 | { 199 | debugRects = []; 200 | return; 201 | } 202 | RenderDebugRects(); 203 | } 204 | 205 | let toggleCapture = 0; 206 | function UpdateDebugPost() 207 | { 208 | if (toggleCapture) 209 | { 210 | ToggleCapture(); 211 | toggleCapture=0; 212 | } 213 | } 214 | 215 | 216 | /////////////////////////////////////////////////////////////////////////////// 217 | // debug controls 218 | 219 | function UpdateDebugControls() 220 | { 221 | if (debug) 222 | { 223 | if (captureOnStart) 224 | { 225 | toggleCapture=1; 226 | captureOnStart= 0; 227 | } 228 | 229 | if (KeyWasPressed(49)) 230 | toggleCapture=1; 231 | if (KeyWasPressed(50)) 232 | SaveSnapshot() 233 | if (KeyWasPressed(51)) 234 | debugCollision = !debugCollision; 235 | if (KeyWasPressed(52)) // 4 236 | debugParticles = !debugParticles; 237 | if (KeyWasPressed(53)) // 5 238 | godMode = !godMode; 239 | if (KeyWasPressed(145)) // scroll lock 240 | debug = 0; 241 | if (KeyWasPressed(71)) // g 242 | godMode=1; 243 | } 244 | 245 | if (KeyIsDown(70)&&KeyIsDown(82)&&KeyIsDown(65)&&KeyIsDown(78)&&KeyIsDown(75)) 246 | { 247 | // dev mode - press all keys to spell "FRANK" 248 | if (!debug) 249 | PlaySound(4); 250 | debug = 1; 251 | } 252 | } 253 | 254 | /////////////////////////////////////////////////////////////////////////////// 255 | // capture tools 256 | 257 | let capturer; 258 | function ToggleCapture() 259 | { 260 | if (capturer) 261 | { 262 | capturer.stop(); 263 | capturer.save(); 264 | capturer=0; 265 | return; 266 | } 267 | 268 | capturer = new CCapture({ 269 | display: true, 270 | framerate: 60, 271 | format: "gif", 272 | timeLimit: 60, 273 | workersPath: './' 274 | }); 275 | capturer.start(); 276 | } 277 | 278 | function SaveSnapshot() 279 | { 280 | downloadLink.download="snapshot.png"; 281 | downloadLink.href=mainCanvas.toDataURL("image/png").replace("image/png", "image/octet-stream"); 282 | downloadLink.click(); 283 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /gameEngine.js: -------------------------------------------------------------------------------- 1 | /* 2 | Javascript Game Engine 3 | By Frank Force 2019 4 | 5 | Engine Features 6 | - Engine is separate from game code, I kept it super simple. 7 | - Object oriented system with base class game object. 8 | - Base class object handles physics, collision, rendering, shadows, etc. 9 | - Objects collide with level tiles and can bounce off. 10 | - Engine helper classes and functions like Vector2, Color, and Timer. 11 | - Level is composed of a grid of tiles that can optionally have objects on them (bushes/rocks) 12 | - Automatically tiles level based on what is there. 13 | - Level & ground is cached to offscreen buffer, so all the level, trees, blood splats is only 1 draw call. 14 | - Sound effects audio with my tiny sound lib zzfx. 15 | - Input processing system. 16 | - A simple particle effect system. 17 | */ 18 | 19 | /////////////////////////////////////////////////////////////////////////////// 20 | // config 21 | 'use strict'; 22 | 23 | /////////////////////////////////////////////////////////////////////////////// 24 | // helper functions 25 | 26 | let RGBA = (r=0,g=0,b=0,a=1)=>(`rgba(${r*255|0},${g*255|0},${b*255|0},${a})`); 27 | let PI = Math.PI; 28 | let Rand = (m=1)=>Math.random()*m; 29 | let RandInt = m=>Rand(m)|0; 30 | let RandBetween = (a,b)=>a+Rand(b-a); 31 | let RandIntBetween = (a,b)=>a+RandInt(b-a+1); 32 | let RandVector = (scale=1)=> (new Vector2(scale,0)).Rotate(Rand(2*PI)); 33 | let RandColorBetween = (c1,c2)=> c1.Clone().Lerp(c2,Rand()); 34 | let IsArrayValid = (x,y,size)=> (x>=0 && y>=0 && x < size && y < size); 35 | 36 | let Min=(a, b)=> (a (a>b)? a : b; 38 | let Clamp=(v, min, max)=> Min(Max(v, min), max); 39 | let Percent=(v, a, b)=> (a==b)? 0 : Clamp((v-a)/(b-a), 0, 1); 40 | let Lerp=(p, a, b)=> a + Clamp(p, 0, 1) * (b-a); 41 | let FormatTime=(t)=> 42 | { 43 | let s = (t|0)%60; 44 | return (t/60|0)+':'+(s<10?'0':'')+s; 45 | } 46 | 47 | class Timer 48 | { 49 | constructor() { this.endTime=0; } 50 | Set(timeLeft=0) { this.endTime = time + timeLeft; } 51 | Get() { return this.IsSet()? time - this.endTime : 1e9; } 52 | IsSet() { return this.endTime > 0; } 53 | UnSet() { this.endTime = 0; } 54 | Elapsed() { return !this.IsSet() || time > this.endTime; } 55 | valueOf() { return this.Get(); } 56 | } 57 | 58 | class Vector2 59 | { 60 | constructor(x=0, y=0) { this.x = x; this.y = y; } 61 | Copy(v) { this.x = v.x; this.y = v.y; return this; } 62 | Clone(s=1) { return (new Vector2(this.x, this.y)).Multiply(s); } 63 | Add(v) { (v instanceof Vector2)? (this.x += v.x, this.y += v.y) : (this.x += v, this.y += v); return this; } 64 | Subtract(v) { (this.x -= v.x, this.y -= v.y) ; return this; } 65 | Multiply(v) { (v instanceof Vector2)? (this.x *= v.x, this.y *= v.y) : (this.x *= v, this.y *= v); return this; } 66 | Set(x, y) { this.x = x; this.y = y; return this; } 67 | AddXY(x, y) { this.x += x; this.y += y; return this; } 68 | Normalize(scale=1) { let l = this.Length(); return l > 0 ? this.Multiply(scale/l) : this.Set(scale,0); } 69 | ClampLength(length) { let l = this.Length(); return l > length ? this.Multiply(length/l) : this; } 70 | Rotate(a) { let c=Math.cos(a);let s=Math.sin(a);return this.Set(this.x*c - this.y*s,this.x*s - this.y*c); } 71 | Round() { this.x = Math.round(this.x); this.y = Math.round(this.y); return this; } 72 | Length() { return Math.hypot(this.x, this.y ); } 73 | Distance(v) { return Math.hypot(this.x - v.x, this.y - v.y ); } 74 | Angle() { return Math.atan2(this.y, this.x); }; 75 | Rotation() { return (Math.abs(this.x)>Math.abs(this.y))?(this.x>0?2:0):(this.y>0?1:3); } 76 | Lerp(v,p) { return this.Add(v.Clone().Subtract(this).Multiply(p)); } 77 | DotProduct(v) { return this.x*v.x+this.y*v.y; } 78 | } 79 | 80 | class Color 81 | { 82 | constructor(r=0,g=0,b=0,a=1) { this.r=r;this.g=g;this.b=b;this.a=a; } 83 | Copy(c) { this.r=c.r;this.g=c.g;this.b=c.b;this.a=c.a; return this; } 84 | Clone(s=1) { return new Color(this.r*s, this.g*s, this.b*s, this.a*s); } 85 | //Add(c) { this.r+=c.r;this.g+=c.g;this.b+=c.b;this.a+=c.a; return this; } 86 | Subtract(c) { this.r-=c.r;this.g-=c.g;this.b-=c.b;this.a-=c.a; return this; } 87 | //Multiply(c) { (c instanceof Color)? (this.r*=c.r,this.g*=c.g,this.b*=c.b,this.a*=c.a) : (this.r*=c,this.g*=c,this.b*=c,this.a*=c); return this; } 88 | SetAlpha(a) { this.a=a; return this; } 89 | Lerp(c,p) { return c.Clone().Subtract(c.Clone().Subtract(this).Clone(1-p)); } 90 | RGBA() { return RGBA(this.r, this.g, this.b, this.a); } 91 | } 92 | 93 | /////////////////////////////////////////////////////////////////////////////// 94 | // game object 95 | 96 | class GameObject 97 | { 98 | constructor(pos,tileX,tileY,size=.5,collisionSize=0,health=1) 99 | { 100 | this.pos = pos.Clone(); 101 | this.tileX = tileX; 102 | this.tileY = tileY; 103 | this.size = new Vector2(size,size); 104 | this.collisionSize = collisionSize; 105 | this.health = health; 106 | this.healthMax = health; 107 | this.damageTimer = new Timer(); 108 | this.lifeTimer = new Timer(); 109 | this.lifeTimer.Set(); 110 | this.velocity = new Vector2(); 111 | this.angle = 0; 112 | this.angleVelocity = 0; 113 | this.damping = 1; 114 | this.mirror = 0; 115 | this.height = 0; 116 | this.renderOrder = 0; 117 | this.canBeDamaged = 1; 118 | 119 | gameObjects.push(this); 120 | } 121 | 122 | Update() 123 | { 124 | // apply velocity 125 | let oldPos = this.pos; 126 | let newPos = this.pos.Clone(); 127 | newPos.Add(this.velocity); 128 | 129 | this.pos = newPos; 130 | 131 | // apply physics 132 | this.velocity.Multiply(this.damping); 133 | this.angle += this.angleVelocity; 134 | 135 | if (debugCollision) 136 | DebugRect(this.pos,new Vector2(this.collisionSize,this.collisionSize),'#F00'); 137 | } 138 | 139 | Render() { DrawTile(this.pos,this.size,this.tileX,this.tileY,this.angle,this.mirror,this.height);} 140 | 141 | Heal(health) 142 | { 143 | if (this.IsDead()) 144 | return 0; 145 | 146 | // apply healing 147 | let startHealth = this.health; 148 | this.health = Min(this.health+health,this.healthMax); 149 | return this.health - startHealth; 150 | } 151 | 152 | Damage(damage, damageObject) 153 | { 154 | if (this.IsDead())// || this.GetDamageTime() < .5) 155 | return 0; 156 | 157 | // apply damage 158 | this.damageTimer.Set(); 159 | let startHealth = this.health; 160 | this.health = Max(this.health-damage,0); 161 | if (!this.health) 162 | this.Kill(); 163 | 164 | return startHealth - this.health; 165 | } 166 | 167 | ReflectDamage(direction){ return 0; } 168 | GetLifeTime() { return this.lifeTimer.Get(); } 169 | GetDamageTime() { return this.damageTimer.Get(); } 170 | GetDamageFlashPercent() { return Clamp(1- this.GetDamageTime()/this.damageFlashTime,0,1); } 171 | IsTouching(object) { return this.Distance(object) < object.collisionSize + this.collisionSize; } 172 | IsDead() { return !this.health; } 173 | Kill() { this.health = 0; this.Destroy(); } 174 | Destroy() { gameObjects.splice(gameObjects.indexOf(this), 1); } 175 | Distance(object) 176 | { 177 | // get distance between objects accounting for height 178 | let p1 = this.pos; let p2 = object.pos; 179 | return Math.hypot(p1.x - p2.x, p1.y - p2.y, this.height - object.height); 180 | } 181 | } 182 | 183 | /////////////////////////////////////////////////////////////////////////////// 184 | // core engine 185 | 186 | let cameraScale = 1; 187 | let cameraPos = new Vector2(); 188 | let frame = 0; 189 | let time = 1; 190 | let paused = 0; 191 | let manualPaused = 0; 192 | let timeDelta = 1/60; 193 | let hitRenderPass = 0; 194 | let mainCanvasContext; 195 | let mainCanvasSize = new Vector2(); 196 | let tileSize = 8; 197 | let lastUpdate = 0; 198 | let timeBuffer = 0; 199 | let hadInput = 0; 200 | 201 | function EngineInit() 202 | { 203 | // set the main canvas size to half size of the window 204 | mainCanvasContext = mainCanvas.getContext('2d'); 205 | mainCanvasSize.Set(84,48); 206 | mainCanvas.width = mainCanvasSize.x; 207 | mainCanvas.height = mainCanvasSize.y; 208 | 209 | InitDebug(); 210 | } 211 | 212 | function EngineUpdate() 213 | { 214 | UpdateGamepads(); 215 | 216 | // time regulation, in case running faster then 60 fps, though it causes judder 217 | let now = Date.now(); 218 | if (lastUpdate && !capturer) 219 | { 220 | // limit to 60 fps 221 | let delta = now - lastUpdate; 222 | if (timeBuffer + delta < 0) 223 | { 224 | // running fast 225 | requestAnimationFrame(EngineUpdate); 226 | return; 227 | } 228 | 229 | timeBuffer += delta; 230 | timeBuffer -= timeDelta * 1e3; 231 | if (timeBuffer > timeDelta * 1e3) 232 | timeBuffer = 0; // if running too slow 233 | } 234 | lastUpdate = now; 235 | 236 | if (manualPaused && (gamepadButtonWasPressed[9] || KeyIsDown(27))) 237 | { 238 | gamepadButtonWasPressed[9] = 0; 239 | keyInputData[27].isDown = 0; 240 | manualPaused = 0; 241 | } 242 | 243 | paused = manualPaused || (!debug && !document.hasFocus() && hadInput); 244 | if (paused) 245 | { 246 | // prevent stuck input if focus is lost 247 | mouseIsDown = mouseWasDown = 0; 248 | keyInputData.map(k=>k.wasDown=k.isDown=0); 249 | } 250 | 251 | // fit canvas to window 252 | /*if (!captureMode) 253 | mainCanvasSize.Set(window.innerWidth/2,window.innerHeight/2); 254 | mainCanvas.width = mainCanvasSize.x; 255 | mainCanvas.height = mainCanvasSize.y; 256 | mainCanvasContext.imageSmoothingEnabled = 0;*/ 257 | 258 | // get mouse world pos 259 | let m = mousePosWorld.Copy(mousePos); 260 | m.Subtract(mainCanvasSize.Clone(.5)); 261 | m.Multiply(1/(cameraScale*tileSize)); 262 | m.Add(cameraPos); 263 | 264 | // main update 265 | if (!paused) 266 | { 267 | // debug speed up / slow down 268 | let frames = 1; 269 | if (debug && KeyIsDown(107)) 270 | frames = 4; 271 | if (debug && KeyIsDown(109)) 272 | frames = (debugFrame%4==0); 273 | while(frames--) 274 | { 275 | time = 1+ ++frame * timeDelta 276 | 277 | 278 | UpdateFrame(); 279 | 280 | 281 | 282 | //if (levelFrame >1 && !currentPlayerGhost[levelFrame-1]) 283 | UpdateGameObjects(); 284 | } 285 | } 286 | 287 | // main render 288 | let SortGameObjects = (a,b)=> a.renderOrder-b.renderOrder; 289 | gameObjects.sort(SortGameObjects); 290 | PreRender(); 291 | RenderGameObjects(); 292 | PostRender(); 293 | UpdateDebug(); 294 | 295 | // clear input 296 | mouseWasDown = mouseIsDown; 297 | keyInputData.map(k=>k.wasDown=k.isDown); 298 | requestAnimationFrame(EngineUpdate); 299 | 300 | UpdateDebugPost(); 301 | } 302 | 303 | /////////////////////////////////////////////////////////////////////////////// 304 | // game object system 305 | 306 | let gameObjects = []; 307 | function ClearGameObjects() { gameObjects = []; } 308 | function UpdateGameObjects() 309 | { 310 | // make copy so objects can be removed 311 | let gameObjectsCopy = gameObjects.slice(); 312 | gameObjectsCopy.forEach(o=>o.Update()); 313 | 314 | } 315 | function RenderGameObjects() 316 | { 317 | gameObjects.forEach(o=> 318 | { 319 | o.Render(); 320 | { 321 | // draw the hit flash overlay 322 | hitRenderPass = o.GetDamageFlashPercent(); 323 | if (hitRenderPass) 324 | { 325 | o.Render(); 326 | hitRenderPass = 0; 327 | } 328 | } 329 | }); 330 | } 331 | 332 | /////////////////////////////////////////////////////////////////////////////// 333 | // input 334 | 335 | let mouseMode = 0; 336 | let mouseIsDown = 0; 337 | let mouseWasDown = 0; 338 | let keyInputData = []; 339 | let mousePos = new Vector2(); 340 | let mousePosWorld = new Vector2(); 341 | 342 | let ControlsMultiplex=key=> 343 | { 344 | switch(key) 345 | { 346 | case 87: return(38); 347 | case 83: return(40); 348 | case 65: return(37); 349 | case 68: return(39); 350 | case 104: return(38); 351 | case 98: return(40); 352 | case 100: return(37); 353 | case 102: return(39); 354 | 355 | case 16: return(32); 356 | case 101: return(32); 357 | case 96: return(32); 358 | } 359 | 360 | return key; 361 | } 362 | 363 | //oncontextmenu = function(e) { e.preventDefault(); } 364 | onmousedown = function(e) { if (e.button!=0)return; mouseIsDown=1; mouseMode=1; hadInput=1; } 365 | onmouseup = function(e) { if (e.button!=0)return; mouseIsDown=0; hadInput=1; } 366 | onmousemove = function(e) 367 | { 368 | // convert mouse pos to canvas space 369 | let rect = mainCanvas.getBoundingClientRect(); 370 | mousePos.Set 371 | ( 372 | (e.clientX - rect.left) / rect.width, 373 | (e.clientY - rect.top) / rect.height 374 | ).Multiply(mainCanvasSize); 375 | } 376 | onkeydown = function(e) 377 | { 378 | if (debug && e.keyCode==192) 379 | e.preventDefault(),ToggleDebugConsole(); 380 | if (debug && document.activeElement && document.activeElement.type == 'textarea') 381 | return; 382 | 383 | mouseMode = 0; 384 | let keyCode = ControlsMultiplex(e.keyCode); 385 | keyInputData[keyCode]={isDown:1}; 386 | isUsingGamepad=0; 387 | hadInput=1; 388 | } 389 | onkeyup = function(e) 390 | { 391 | if (debug && document.activeElement && document.activeElement.type == 'textarea') 392 | return; 393 | 394 | let keyCode = ControlsMultiplex(e.keyCode); 395 | if ( keyInputData[keyCode] ) keyInputData[keyCode].isDown=0; 396 | hadInput=1; 397 | } 398 | 399 | function MouseWasPressed() { return mouseIsDown && !mouseWasDown; } 400 | function KeyIsDown(key) { return keyInputData[key]? keyInputData[key].isDown : 0; } 401 | function KeyWasPressed(key) { return KeyIsDown(key) && !keyInputData[key].wasDown; } 402 | function ClearInput() { keyInputData.map(k=>k.wasDown=k.isDown=0);mouseIsDown=mouseWasDown=0; } 403 | 404 | /////////////////////////////////////////////////////////////////////////////// 405 | // rendering 406 | 407 | function DrawScreenTile(x,y,size,tileX,tileY) 408 | { 409 | mainCanvasContext.drawImage(tileImage,tileX*tileSize,tileY*tileSize,tileSize,tileSize, x-size, y-size, 2*size, 2*size); 410 | } 411 | 412 | function SetCanvasTransform(pos,size,angle=0,height=0) 413 | { 414 | // create canvas transform from world space to screen space 415 | mainCanvasContext.save(); 416 | let drawPos = pos.Clone(); 417 | drawPos.y -= height; 418 | drawPos.Multiply(tileSize*cameraScale).Round(); 419 | drawPos.Subtract(cameraPos.Clone(tileSize*cameraScale)); 420 | drawPos.Add(mainCanvasSize.Clone(.5)); 421 | mainCanvasContext.translate(drawPos.x|0, drawPos.y|0); 422 | 423 | let s = size.Clone(tileSize); 424 | if (angle) 425 | mainCanvasContext.rotate(angle); 426 | mainCanvasContext.scale(cameraScale,cameraScale); 427 | } 428 | 429 | function DrawTile(pos,size,tileX=0,tileY=0,angle=0,mirror=0,height=0,tilePixelSize=tileSize) 430 | { 431 | // render a tile at a world space position 432 | SetCanvasTransform(pos,size,angle,height); 433 | 434 | let image = tileImage; 435 | 436 | // shrink size of tile to fix bleeding on edges 437 | let renderTileShrink = .25; 438 | 439 | /// render the tile 440 | let s = size.Clone(tileSize); 441 | mainCanvasContext.imageSmoothingEnabled = 0; 442 | mainCanvasContext.scale(mirror?-s.x:s.x,s.y); 443 | mainCanvasContext.drawImage(image, 444 | tileX*tilePixelSize+renderTileShrink, 445 | tileY*tilePixelSize+renderTileShrink, 446 | tilePixelSize-2*renderTileShrink, 447 | tilePixelSize-2*renderTileShrink, -.5, -.5, 1, 1); 448 | mainCanvasContext.restore(); 449 | mainCanvasContext.globalAlpha = 1; 450 | } 451 | 452 | /* 453 | function DrawText(text, x, y, size,textAlign='center',lineWidth=1,color='#000',strokeColor='#fff',context=mainCanvasContext) 454 | { 455 | context.fillStyle=color; 456 | context.font = `900 ${size}px arial` 457 | context.textAlign=textAlign; 458 | context.textBaseline='middle'; 459 | context.fillText(text,x,y); 460 | if (lineWidth) 461 | { 462 | context.lineWidth=lineWidth; 463 | context.strokeStyle=strokeColor; 464 | context.strokeText(text,x,y); 465 | } 466 | }*/ 467 | 468 | /////////////////////////////////////////////////////////////////////////////// 469 | // particle system 470 | 471 | class Particle 472 | { 473 | constructor(emitter,pos,velocity,size,lifeTime,startColor,endColor) 474 | { 475 | this.emitter = emitter; 476 | this.pos = pos; 477 | this.velocity = velocity; 478 | this.size = size; 479 | this.lifeTime = lifeTime; 480 | this.startColor = startColor; 481 | this.endColor = endColor; 482 | this.lifeTimer = new Timer(); 483 | this.lifeTimer.Set(); 484 | } 485 | 486 | Update() 487 | { 488 | // update physics 489 | this.pos.Add(this.velocity.Multiply(.9)); 490 | 491 | // remove if dead 492 | if (this.lifeTimer.Get() > this.lifeTime) 493 | this.emitter.particles.splice(this.emitter.particles.indexOf(this),1); 494 | 495 | if (debugCollision) 496 | DebugRect(this.pos, new Vector2(this.size,this.size), '#0FF'); 497 | } 498 | 499 | Render() 500 | { 501 | // get the color 502 | let p = Percent(this.lifeTimer.Get(), 0, this.lifeTime); 503 | let c = this.startColor.Clone().Lerp(this.endColor, p); 504 | //c.a *= p<.1? p /.1 : 1; // fade in alpha 505 | c.a = 1; 506 | mainCanvasContext.fillStyle=c.RGBA(); 507 | 508 | // get the size 509 | let size = this.size * cameraScale * tileSize * Lerp(p,1,0); 510 | 511 | // get the screen pos and render 512 | let pos = this.pos.Clone() 513 | .Subtract(cameraPos) 514 | .Multiply(tileSize*cameraScale) 515 | .Add(mainCanvasSize.Clone(.5)) 516 | .Add(-size); 517 | mainCanvasContext.fillRect(pos.x|0, pos.y|0, 2*size|0, 2*size|0); 518 | } 519 | } 520 | 521 | class ParticleEmitter extends GameObject 522 | { 523 | constructor( pos, emitSize, particleSize, color1, color2 ) 524 | { 525 | super(pos,0,0,emitSize); 526 | this.particleSize=particleSize; 527 | this.color1=color1.Clone(); 528 | this.color2=color2.Clone(); 529 | this.particles=[]; 530 | this.emitTimeBuffer=0; 531 | } 532 | 533 | Update() 534 | { 535 | // update particles 536 | this.particles.forEach(particle=>particle.Update()); 537 | 538 | if (this.GetLifeTime() <= .05) 539 | { 540 | // emit new particles 541 | let secondsPerEmit = 1/200; 542 | this.emitTimeBuffer += timeDelta; 543 | while (this.emitTimeBuffer > secondsPerEmit) 544 | { 545 | this.emitTimeBuffer -= secondsPerEmit; 546 | this.AddParticle(); 547 | } 548 | } 549 | else if (!this.particles.length) 550 | { 551 | // go away when all particles are gone 552 | this.Destroy(); 553 | } 554 | 555 | if (debugCollision) 556 | DebugRect(this.pos, new Vector2(this.size,this.size), '#00F'); 557 | 558 | super.Update(); 559 | } 560 | 561 | Render() { this.particles.forEach(p=>p.Render()); } 562 | 563 | AddParticle() 564 | { 565 | // create a new particle with random settings 566 | this.particles.push 567 | ( 568 | new Particle 569 | ( 570 | this, 571 | this.pos.Clone().Add(RandVector(Rand(this.size.x))), 572 | RandVector(Rand(.2)), 573 | RandBetween(this.particleSize,2*this.particleSize), 574 | RandBetween(.5,1), 575 | RandColorBetween(this.color1,this.color2), 576 | RandColorBetween(this.color1,this.color2).SetAlpha(0) 577 | ) 578 | ); 579 | } 580 | } 581 | 582 | //////////////////////////////////////////////////////////////////// 583 | // gamepad API 584 | 585 | let isUsingGamepad = 0; 586 | let gamepadLeft = new Vector2(); 587 | let gamepadRight = new Vector2(); 588 | let gamepadButtonIsDown = []; 589 | let gamepadButtonWasPressed = []; 590 | let deadZone = .3; 591 | let maxZone = .8; 592 | 593 | function ApplyDeadZone(v) 594 | { 595 | if (v>deadZone) 596 | return (v-=deadZone)>maxZone? 1 : v/(maxZone-deadZone); 597 | else if (v<-deadZone) 598 | return (v+=deadZone)<-maxZone? -1 : v/(maxZone-deadZone); 599 | return 0; 600 | } 601 | 602 | function UpdateGamepads() 603 | { 604 | if (!("getGamepads" in navigator)) 605 | return; 606 | 607 | let gamepad = navigator.getGamepads()[0]; 608 | if (gamepad && gamepad.axes.length>=4 && gamepad.buttons.length>=1) 609 | { 610 | gamepadLeft.x = ApplyDeadZone(gamepad.axes[0]); 611 | gamepadLeft.y = ApplyDeadZone(gamepad.axes[1]); 612 | gamepadRight.x = ApplyDeadZone(gamepad.axes[2]); 613 | gamepadRight.y = ApplyDeadZone(gamepad.axes[3]); 614 | if (Math.abs(gamepadLeft.x)+Math.abs(gamepadLeft.y)>.6) 615 | isUsingGamepad=1; 616 | 617 | for(let i=16; i--;) 618 | { 619 | gamepadButtonWasPressed[i] = !gamepadButtonIsDown[i] && gamepad.buttons[i].pressed; 620 | gamepadButtonIsDown[i] = gamepad.buttons[i].pressed; 621 | if (gamepadButtonIsDown[i]) 622 | isUsingGamepad=1; 623 | } 624 | 625 | if (gamepadButtonIsDown[12]||gamepadButtonIsDown[13]||gamepadButtonIsDown[14]||gamepadButtonIsDown[15]) 626 | { 627 | // dpad 628 | gamepadLeft.Set(0,0); 629 | if (gamepadButtonIsDown[12]) 630 | gamepadLeft.y -= 1; 631 | if (gamepadButtonIsDown[13]) 632 | gamepadLeft.y += 1; 633 | if (gamepadButtonIsDown[14]) 634 | gamepadLeft.x -= 1; 635 | if (gamepadButtonIsDown[15]) 636 | gamepadLeft.x += 1; 637 | gamepadLeft.Normalize(); 638 | } 639 | 640 | gamepadButtonIsDown[2] |= gamepadButtonIsDown[7]; 641 | gamepadButtonWasPressed[2] |= gamepadButtonWasPressed[7]; 642 | gamepadButtonIsDown[0] |= gamepadButtonIsDown[6]; 643 | gamepadButtonWasPressed[0] |= gamepadButtonWasPressed[6]; 644 | 645 | } 646 | 647 | if (!isUsingGamepad) 648 | { 649 | gamepadLeft.Set(0,0); 650 | gamepadRight.Set(0,0); 651 | for(let b of gamepadButtonIsDown) 652 | b = 0; 653 | for(let b of gamepadButtonWasPressed) 654 | b = 0; 655 | } 656 | 657 | if (isUsingGamepad) 658 | { 659 | hadInput=1; 660 | mouseMode = 0; 661 | } 662 | } -------------------------------------------------------------------------------- /CCapture.all.min.js: -------------------------------------------------------------------------------- 1 | "use strict";function download(t,e,i){function n(t){var e=t.split(/[:;,]/),i=e[1],n="base64"==e[2]?atob:decodeURIComponent,r=n(e.pop()),o=r.length,a=0,s=new Uint8Array(o);for(a;a>8,this.data[this.pos++]=t},t.prototype.writeDoubleBE=function(t){for(var e=new Uint8Array(new Float64Array([t]).buffer),i=e.length-1;i>=0;i--)this.writeByte(e[i])},t.prototype.writeFloatBE=function(t){for(var e=new Uint8Array(new Float32Array([t]).buffer),i=e.length-1;i>=0;i--)this.writeByte(e[i])},t.prototype.writeString=function(t){for(var e=0;e>8),this.writeU8(t);break;case 3:this.writeU8(32|t>>16),this.writeU8(t>>8),this.writeU8(t);break;case 4:this.writeU8(16|t>>24),this.writeU8(t>>16),this.writeU8(t>>8),this.writeU8(t);break;case 5:this.writeU8(8|t/4294967296&7),this.writeU8(t>>24),this.writeU8(t>>16),this.writeU8(t>>8),this.writeU8(t);break;default:throw new RuntimeException("Bad EBML VINT size "+e)}},t.prototype.measureEBMLVarInt=function(t){if(t<127)return 1;if(t<16383)return 2;if(t<2097151)return 3;if(t<268435455)return 4;if(t<34359738367)return 5;throw new RuntimeException("EBML VINT size not supported "+t)},t.prototype.writeEBMLVarInt=function(t){this.writeEBMLVarIntWidth(t,this.measureEBMLVarInt(t))},t.prototype.writeUnsignedIntBE=function(t,e){switch(void 0===e&&(e=this.measureUnsignedInt(t)),e){case 5:this.writeU8(Math.floor(t/4294967296));case 4:this.writeU8(t>>24);case 3:this.writeU8(t>>16);case 2:this.writeU8(t>>8);case 1:this.writeU8(t);break;default:throw new RuntimeException("Bad UINT size "+e)}},t.prototype.measureUnsignedInt=function(t){return t<256?1:t<65536?2:t<1<<24?3:t<4294967296?4:5},t.prototype.getAsDataArray=function(){if(this.posthis.length)throw"Seeking beyond the end of file is not allowed";this.pos=t},this.write=function(e){var i={offset:this.pos,data:e,length:r(e)},f=i.offset>=this.length;this.pos+=i.length,this.length=Math.max(this.length,this.pos),a=a.then(function(){if(h)return new Promise(function(e,r){n(i.data).then(function(n){var r=0,o=Buffer.from(n.buffer),a=function(n,o,s){r+=o,r>=s.length?e():t.write(h,s,r,s.length-r,i.offset+r,a)};t.write(h,o,0,o.length,i.offset,a)})});if(s)return new Promise(function(t,e){s.onwriteend=t,s.seek(i.offset),s.write(new Blob([i.data]))});if(!f)for(var e=0;e=r.offset+r.length)){if(i.offsetr.offset+r.length)throw new Error("Overwrite crosses blob boundaries");return i.offset==r.offset&&i.length==r.length?void(r.data=i.data):n(r.data).then(function(t){return r.data=t,n(i.data)}).then(function(t){i.data=t,r.data.set(i.data,i.offset-r.offset)})}}o.push(i)})},this.complete=function(t){return a=h||s?a.then(function(){return null}):a.then(function(){for(var e=[],i=0;i0&&e.trackNumber<127))throw"TrackNumber must be > 0 and < 127";return i.writeEBMLVarInt(e.trackNumber),i.writeU16BE(e.timecode),i.writeByte(128),{id:163,data:[i.getAsDataArray(),e.frame]}}function l(t){return{id:524531317,data:[{id:231,data:Math.round(t.timecode)}]}}function c(t,e,i){_.push({id:187,data:[{id:179,data:e},{id:183,data:[{id:247,data:t},{id:241,data:a(i)}]}]})}function p(){var e={id:475249515,data:_},i=new t(16+32*_.length);h(i,S.pos,e),S.write(i.getAsDataArray()),D.Cues.positionEBML.data=a(e.offset)}function m(){if(0!=T.length){for(var e=0,i=0;i=E&&m()}function y(){var e=new t(x.size),i=S.pos;h(e,x.dataOffset,x.data),S.seek(x.dataOffset),S.write(e.getAsDataArray()),S.seek(i)}function v(){var e=new t(8),i=S.pos;e.writeDoubleBE(U),S.seek(M.dataOffset),S.write(e.getAsDataArray()),S.seek(i)}var b,k,B,x,E=5e3,A=1,L=!1,T=[],U=0,F=0,I={quality:.95,fileWriter:null,fd:null,frameDuration:null,frameRate:null},D={Cues:{id:new Uint8Array([28,83,187,107]),positionEBML:null},SegmentInfo:{id:new Uint8Array([21,73,169,102]),positionEBML:null},Tracks:{id:new Uint8Array([22,84,174,107]),positionEBML:null}},M={id:17545,data:new s(0)},_=[],S=new e(n.fileWriter||n.fd);this.addFrame=function(t){if(L){if(t.width!=b||t.height!=k)throw"Frame size differs from previous frames"}else b=t.width,k=t.height,u(),L=!0;var e=r(t,{quality:n.quality});if(!e)throw"Couldn't decode WebP frame, does the browser support WebP?";g({frame:o(e),duration:n.frameDuration})},this.complete=function(){return m(),p(),y(),v(),S.complete("video/webm")},this.getWrittenSize=function(){return S.length},n=i(I,n||{}),w()}};"undefined"!=typeof module&&"undefined"!=typeof module.exports?module.exports=t(require("./ArrayBufferDataStream"),require("./BlobBuffer")):window.WebMWriter=t(ArrayBufferDataStream,BlobBuffer)}(),function(){function t(t){var e,i=new Uint8Array(t);for(e=0;e>18&63]+o[t>>12&63]+o[t>>6&63]+o[63&t]}var i,n,r,a=t.length%3,s="";for(i=0,r=t.length-a;in&&(e.push({blocks:o,length:i}),o=[],i=0),o.push(t),i+=t.headerLength+t.inputLength}),e.push({blocks:o,length:i}),e.forEach(function(e){var i=new Uint8Array(e.length),n=0;e.blocks.forEach(function(t){i.set(t.header,n),n+=t.headerLength,i.set(t.input,n),n+=t.inputLength}),t.push(i)}),t.push(new Uint8Array(2*r)),new Blob(t,{type:"octet/stream"})},t.prototype.clear=function(){this.written=0,this.out=n.clean(e)},window.Tar=t}(),function(t){function e(t,i){if({}.hasOwnProperty.call(e.cache,t))return e.cache[t];var n=e.resolve(t);if(!n)throw new Error("Failed to resolve module "+t);var r={id:t,require:e,filename:t,exports:{},loaded:!1,parent:i,children:[]};i&&i.children.push(r);var o=t.slice(0,t.lastIndexOf("/")+1);return e.cache[t]=r.exports,n.call(r.exports,r,r.exports,o,t),r.loaded=!0,e.cache[t]=r.exports}e.modules={},e.cache={},e.resolve=function(t){return{}.hasOwnProperty.call(e.modules,t)?e.modules[t]:void 0},e.define=function(t,i){e.modules[t]=i};var i=function(e){return e="/",{title:"browser",version:"v0.10.26",browser:!0,env:{},argv:[],nextTick:t.setImmediate||function(t){setTimeout(t,0)},cwd:function(){return e},chdir:function(t){e=t}}}();e.define("/gif.coffee",function(t,i,n,r){function o(t,e){return{}.hasOwnProperty.call(t,e)}function a(t,e){for(var i=0,n=e.length;ithis.frames.length;0<=this.frames.length?++e:--e)t.push(e);return t}.apply(this,arguments),n=0,r=i.length;ne;0<=e?++i:--i)t.push(i);return t}.apply(this,arguments),n=0,r=i.length;nt;this.freeWorkers.length<=t?++i:--i)e.push(i);return e}.apply(this,arguments).forEach(function(t){return function(e){var i;return console.log("spawning worker "+e),i=new Worker(t.options.workerScript),i.onmessage=function(t){return function(e){return t.activeWorkers.splice(t.activeWorkers.indexOf(i),1),t.freeWorkers.push(i),t.frameFinished(e.data)}}(t),t.freeWorkers.push(i)}}(this)),t},e.prototype.frameFinished=function(t){return console.log("frame "+t.index+" finished - "+this.activeWorkers.length+" active"),this.finishedFrames++,this.emit("progress",this.finishedFrames/this.frames.length),this.imageParts[t.index]=t,a(null,this.imageParts)?this.renderNextFrame():this.finishRendering()},e.prototype.finishRendering=function(){var t,e,i,n,r,o,a;r=0;for(var s=0,h=this.imageParts.length;s=this.frames.length?void 0:(t=this.frames[this.nextFrame++],i=this.freeWorkers.shift(),e=this.getTask(t),console.log("starting frame "+(e.index+1)+" of "+this.frames.length),this.activeWorkers.push(i),i.postMessage(e))},e.prototype.getContextData=function(t){return t.getImageData(0,0,this.options.width,this.options.height).data},e.prototype.getImageData=function(t){var e;return null!=this._canvas||(this._canvas=document.createElement("canvas"),this._canvas.width=this.options.width,this._canvas.height=this.options.height),e=this._canvas.getContext("2d"),e.setFill=this.options.background,e.fillRect(0,0,this.options.width,this.options.height),e.drawImage(t,0,0),this.getContextData(e)},e.prototype.getTask=function(t){var e,i;if(e=this.frames.indexOf(t),i={index:e,last:e===this.frames.length-1,delay:t.delay,transparent:t.transparent,width:this.options.width,height:this.options.height,quality:this.options.quality,repeat:this.options.repeat,canTransfer:"chrome"===h.name},null!=t.data)i.data=t.data;else if(null!=t.context)i.data=this.getContextData(t.context);else{if(null==t.image)throw new Error("Invalid frame");i.data=this.getImageData(t.image)}return i},e}(u),t.exports=l}),e.define("/browser.coffee",function(t,e,i,n){var r,o,a,s,h;s=navigator.userAgent.toLowerCase(),a=navigator.platform.toLowerCase(),h=s.match(/(opera|ie|firefox|chrome|version)[\s\/:]([\w\d\.]+)?.*?(safari|version[\s\/:]([\w\d\.]+)|$)/)||[null,"unknown",0],o="ie"===h[1]&&document.documentMode,r={name:"version"===h[1]?h[3]:h[1],version:o||parseFloat("opera"===h[1]&&h[4]?h[4]:h[2]),platform:{name:s.match(/ip(?:ad|od|hone)/)?"ios":(s.match(/(?:webos|android)/)||a.match(/mac|win|linux/)||["other"])[0]}},r[r.name]=!0,r[r.name+parseInt(r.version,10)]=!0,r.platform[r.platform.name]=!0,t.exports=r}),e.define("events",function(t,e,n,r){i.EventEmitter||(i.EventEmitter=function(){});var o=e.EventEmitter=i.EventEmitter,a="function"==typeof Array.isArray?Array.isArray:function(t){return"[object Array]"===Object.prototype.toString.call(t)},s=10;o.prototype.setMaxListeners=function(t){this._events||(this._events={}),this._events.maxListeners=t},o.prototype.emit=function(t){if("error"===t&&(!this._events||!this._events.error||a(this._events.error)&&!this._events.error.length))throw arguments[1]instanceof Error?arguments[1]:new Error("Uncaught, unspecified 'error' event.");if(!this._events)return!1;var e=this._events[t];if(!e)return!1;if("function"!=typeof e){if(a(e)){for(var i=Array.prototype.slice.call(arguments,1),n=e.slice(),r=0,o=n.length;r0&&this._events[t].length>i&&(this._events[t].warned=!0,console.error("(node) warning: possible EventEmitter memory leak detected. %d listeners added. Use emitter.setMaxListeners() to increase limit.",this._events[t].length),console.trace())}this._events[t].push(e)}else this._events[t]=[this._events[t],e];else this._events[t]=e;return this},o.prototype.on=o.prototype.addListener,o.prototype.once=function(t,e){var i=this;return i.on(t,function n(){i.removeListener(t,n),e.apply(this,arguments)}),this},o.prototype.removeListener=function(t,e){if("function"!=typeof e)throw new Error("removeListener only takes instances of Function");if(!this._events||!this._events[t])return this;var i=this._events[t];if(a(i)){var n=i.indexOf(e);if(n<0)return this;i.splice(n,1),0==i.length&&delete this._events[t]}else this._events[t]===e&&delete this._events[t];return this},o.prototype.removeAllListeners=function(t){return t&&this._events&&this._events[t]&&(this._events[t]=null),this},o.prototype.listeners=function(t){return this._events||(this._events={}),this._events[t]||(this._events[t]=[]),a(this._events[t])||(this._events[t]=[this._events[t]]),this._events[t]}}),t.GIF=e("/gif.coffee")}.call(this,this),function(){function t(t){return t&&t.Object===Object?t:null}function e(t){return String("0000000"+t).slice(-7)}function i(){function t(){return Math.floor(65536*(1+Math.random())).toString(16).substring(1)}return t()+t()+"-"+t()+"-"+t()+"-"+t()+"-"+t()+t()+t()}function n(t){var e={};this.settings=t,this.on=function(t,i){e[t]=i},this.emit=function(t){var i=e[t];i&&i.apply(null,Array.prototype.slice.call(arguments,1))},this.filename=t.name||i(),this.extension="",this.mimeType=""}function r(t){n.call(this,t),this.extension=".tar",this.mimeType="application/x-tar",this.fileExtension="",this.baseFilename=this.filename,this.tape=null,this.count=0,this.part=1,this.frames=0}function o(t){r.call(this,t),this.type="image/png",this.fileExtension=".png"}function a(t){r.call(this,t),this.type="image/jpeg",this.fileExtension=".jpg",this.quality=t.quality/100||.8}function s(t){var e=document.createElement("canvas");"image/webp"!==e.toDataURL("image/webp").substr(5,10)&&console.log("WebP not supported - try another export format"),n.call(this,t),this.quality=t.quality/100||.8,this.extension=".webm",this.mimeType="video/webm",this.baseFilename=this.filename,this.framerate=t.framerate,this.frames=0,this.part=1,this.videoWriter=new WebMWriter({quality:this.quality,fileWriter:null,fd:null,frameRate:this.framerate})}function h(t){n.call(this,t),t.quality=t.quality/100||.8,this.encoder=new FFMpegServer.Video(t),this.encoder.on("process",function(){this.emit("process")}.bind(this)),this.encoder.on("finished",function(t,e){var i=this.callback;i&&(this.callback=void 0,i(t,e))}.bind(this)),this.encoder.on("progress",function(t){this.settings.onProgress&&this.settings.onProgress(t)}.bind(this)),this.encoder.on("error",function(t){alert(JSON.stringify(t,null,2))}.bind(this))}function f(t){n.call(this,t),this.framerate=this.settings.framerate,this.type="video/webm",this.extension=".webm",this.stream=null,this.mediaRecorder=null,this.chunks=[]}function u(t){n.call(this,t),t.quality=31-(30*t.quality/100||10),t.workers=t.workers||4,this.extension=".gif",this.mimeType="image/gif",this.canvas=document.createElement("canvas"),this.ctx=this.canvas.getContext("2d"),this.sizeSet=!1,this.encoder=new GIF({workers:t.workers,quality:t.quality,workerScript:t.workersPath+"gif.worker.js"}),this.encoder.on("progress",function(t){this.settings.onProgress&&this.settings.onProgress(t)}.bind(this)),this.encoder.on("finished",function(t){var e=this.callback;e&&(this.callback=void 0,e(t))}.bind(this))}function d(t){function e(){function t(){return this._hooked||(this._hooked=!0,this._hookedTime=this.currentTime||0,this.pause(),it.push(this)),this._hookedTime+M.startTime}b("Capturer start"),U=window.Date.now(),T=U+M.startTime,I=window.performance.now(),F=I+M.startTime,window.Date.prototype.getTime=function(){return T},window.Date.now=function(){return T},window.setTimeout=function(t,e){var i={callback:t,time:e,triggerTime:T+e};return _.push(i),b("Timeout set to "+i.time),i},window.clearTimeout=function(t){for(var e=0;e<_.length;e++)_[e]!=t||(_.splice(e,1),b("Timeout cleared"))},window.setInterval=function(t,e){var i={callback:t,time:e,triggerTime:T+e};return S.push(i),b("Interval set to "+i.time),i},window.clearInterval=function(t){return b("clear Interval"),null},window.requestAnimationFrame=function(t){W.push(t)},window.performance.now=function(){return F};try{Object.defineProperty(HTMLVideoElement.prototype,"currentTime",{get:t}),Object.defineProperty(HTMLAudioElement.prototype,"currentTime",{get:t})}catch(t){b(t)}}function i(){e(),D.start(),R=!0}function n(){R=!1,D.stop(),l()}function r(t,e){Z(t,0,e)}function d(){r(y)}function l(){b("Capturer stop"),window.setTimeout=Z,window.setInterval=J,window.clearInterval=Y,window.clearTimeout=$,window.requestAnimationFrame=Q,window.Date.prototype.getTime=et,window.Date.now=X,window.performance.now=tt}function c(){var t=C/M.framerate;(M.frameLimit&&C>=M.frameLimit||M.timeLimit&&t>=M.timeLimit)&&(n(),v());var e=new Date(null);e.setSeconds(t),M.motionBlurFrames>2?j.textContent="CCapture "+M.format+" | "+C+" frames ("+O+" inter) | "+e.toISOString().substr(11,8):j.textContent="CCapture "+M.format+" | "+C+" frames | "+e.toISOString().substr(11,8)}function p(t){N.width===t.width&&N.height===t.height||(N.width=t.width,N.height=t.height,z=new Uint16Array(N.height*N.width*4),V.fillStyle="#0",V.fillRect(0,0,N.width,N.height))}function m(t){V.drawImage(t,0,0),q=V.getImageData(0,0,N.width,N.height);for(var e=0;e2?(p(t),m(t),O>=.5*M.motionBlurFrames?w():d()):(D.add(t),C++,b("Full Frame! "+C)));return R}function y(){var t=1e3/M.framerate,e=(C+O/M.motionBlurFrames)*t;T=U+e,F=I+e,it.forEach(function(t){t._hookedTime=e/1e3}),c(),b("Frame: "+C+" "+O);for(var i=0;i<_.length;i++)T>=_[i].triggerTime&&(r(_[i].callback),_.splice(i,1));for(var i=0;i=S[i].triggerTime&&(r(S[i].callback),S[i].triggerTime+=S[i].time);W.forEach(function(t){r(t,T-k)}),W=[]}function v(t){t||(t=function(t){return download(t,D.filename+D.extension,D.mimeType),!1}),D.save(t)}function b(t){A&&console.log(t)}function B(t,e){P[t]=e}function x(t){var e=P[t];e&&e.apply(null,Array.prototype.slice.call(arguments,1))}function E(t){x("progress",t)}var A,L,T,U,F,I,d,D,M=t||{},_=(new Date,[]),S=[],C=0,O=0,W=[],R=!1,P={};M.framerate=M.framerate||60,M.motionBlurFrames=2*(M.motionBlurFrames||1),A=M.verbose||!1,L=M.display||!1,M.step=1e3/M.framerate,M.timeLimit=M.timeLimit||0,M.frameLimit=M.frameLimit||0,M.startTime=M.startTime||0;var j=document.createElement("div");j.style.position="absolute",j.style.left=j.style.top=0,j.style.backgroundColor="black",j.style.fontFamily="monospace",j.style.fontSize="11px",j.style.padding="5px",j.style.color="red",j.style.zIndex=1e5,M.display&&document.body.appendChild(j);var z,q,N=document.createElement("canvas"),V=N.getContext("2d");b("Step is set to "+M.step+"ms");var G={gif:u,webm:s,ffmpegserver:h,png:o,jpg:a,"webm-mediarecorder":f},H=G[M.format];if(!H)throw"Error: Incorrect or missing format: Valid formats are "+Object.keys(G).join(", ");if(D=new H(M),D.step=d,D.on("process",y),D.on("progress",E),"performance"in window==0&&(window.performance={}),Date.now=Date.now||function(){return(new Date).getTime()},"now"in window.performance==0){var K=Date.now();performance.timing&&performance.timing.navigationStart&&(K=performance.timing.navigationStart),window.performance.now=function(){return Date.now()-K}}var Z=window.setTimeout,J=window.setInterval,Y=window.clearInterval,$=window.clearTimeout,Q=window.requestAnimationFrame,X=window.Date.now,tt=window.performance.now,et=window.Date.prototype.getTime,it=[];return{start:i,capture:g,stop:n,save:v,on:B}}var l={function:!0,object:!0},c=(parseFloat,parseInt,l[typeof exports]&&exports&&!exports.nodeType?exports:void 0),p=l[typeof module]&&module&&!module.nodeType?module:void 0,m=p&&p.exports===c?c:void 0,w=t(c&&p&&"object"==typeof global&&global),g=t(l[typeof self]&&self),y=t(l[typeof window]&&window),v=t(l[typeof this]&&this),b=w||y!==(v&&v.window)&&y||g||v||Function("return this")();"gc"in window||(window.gc=function(){}),HTMLCanvasElement.prototype.toBlob||Object.defineProperty(HTMLCanvasElement.prototype,"toBlob",{value:function(t,e,i){for(var n=atob(this.toDataURL(e,i).split(",")[1]),r=n.length,o=new Uint8Array(r),a=0;a0&&this.frames/this.settings.framerate>=this.settings.autoSaveTime?this.save(function(t){this.filename=this.baseFilename+"-part-"+e(this.part),download(t,this.filename+this.extension,this.mimeType);var i=this.count;this.dispose(),this.count=i+1,this.part++,this.filename=this.baseFilename+"-part-"+e(this.part),this.frames=0,this.step()}.bind(this)):(this.count++,this.frames++,this.step())}.bind(this),i.readAsArrayBuffer(t)},r.prototype.save=function(t){t(this.tape.save())},r.prototype.dispose=function(){this.tape=new Tar,this.count=0},o.prototype=Object.create(r.prototype),o.prototype.add=function(t){t.toBlob(function(t){r.prototype.add.call(this,t)}.bind(this),this.type)},a.prototype=Object.create(r.prototype),a.prototype.add=function(t){t.toBlob(function(t){r.prototype.add.call(this,t)}.bind(this),this.type,this.quality)},s.prototype=Object.create(n.prototype),s.prototype.start=function(t){this.dispose()},s.prototype.add=function(t){this.videoWriter.addFrame(t),this.settings.autoSaveTime>0&&this.frames/this.settings.framerate>=this.settings.autoSaveTime?this.save(function(t){this.filename=this.baseFilename+"-part-"+e(this.part),download(t,this.filename+this.extension,this.mimeType),this.dispose(),this.part++,this.filename=this.baseFilename+"-part-"+e(this.part),this.step()}.bind(this)):(this.frames++,this.step())},s.prototype.save=function(t){this.videoWriter.complete().then(t)},s.prototype.dispose=function(t){this.frames=0,this.videoWriter=new WebMWriter({quality:this.quality,fileWriter:null,fd:null,frameRate:this.framerate})},h.prototype=Object.create(n.prototype),h.prototype.start=function(){this.encoder.start(this.settings)},h.prototype.add=function(t){this.encoder.add(t)},h.prototype.save=function(t){this.callback=t,this.encoder.end()},h.prototype.safeToProceed=function(){return this.encoder.safeToProceed()},f.prototype=Object.create(n.prototype),f.prototype.add=function(t){this.stream||(this.stream=t.captureStream(this.framerate),this.mediaRecorder=new MediaRecorder(this.stream),this.mediaRecorder.start(),this.mediaRecorder.ondataavailable=function(t){this.chunks.push(t.data)}.bind(this)),this.step()},f.prototype.save=function(t){this.mediaRecorder.onstop=function(e){var i=new Blob(this.chunks,{type:"video/webm"});this.chunks=[],t(i)}.bind(this),this.mediaRecorder.stop()},u.prototype=Object.create(n.prototype),u.prototype.add=function(t){this.sizeSet||(this.encoder.setOption("width",t.width),this.encoder.setOption("height",t.height),this.sizeSet=!0),this.canvas.width=t.width,this.canvas.height=t.height,this.ctx.drawImage(t,0,0),this.encoder.addFrame(this.ctx,{copy:!0,delay:this.settings.step}),this.step()},u.prototype.save=function(t){this.callback=t,this.encoder.render()},(y||g||{}).CCapture=d,"function"==typeof define&&"object"==typeof define.amd&&define.amd?define(function(){return d}):c&&p?(m&&((p.exports=d).CCapture=d),c.CCapture=d):b.CCapture=d}(); -------------------------------------------------------------------------------- /game.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Egg Time Rewind 4 | By Frank Force 2020 5 | 6 | */ 7 | 8 | "use strict"; // strict mode 9 | /////////////////////////////////////////////////////////////////////////////// 10 | // debug config 11 | 12 | godMode=0; 13 | //debug=1; 14 | //captureMode=1; 15 | //debugCanvas=1; 16 | //debugCollision=1; 17 | soundEnable=1; 18 | let spawnEnemies = 1; 19 | //captureOnStart = 1; 20 | 21 | let color1 = new Color(199/255,240/255,216/255) 22 | let color2 = new Color(67/255,82/255,61/255) 23 | 24 | /////////////////////////////////////////////////////////////////////////////// 25 | // init 26 | 27 | let gameMode = 1; 28 | let level = 0; 29 | let levelNumber = 0; 30 | let levelTimer = new Timer(); 31 | let loadNextLevel = 0; 32 | let levelFrame = 0; 33 | let playerScore = 0; 34 | let levelEggHealth = 0; 35 | let egg = 0; 36 | let eggTimer = new Timer(); 37 | let newHighScore = 0; 38 | 39 | let player; 40 | let playerLives = 0; 41 | let playerGhosts = []; 42 | let currentPlayerGhost = []; 43 | 44 | function Init() 45 | { 46 | InitText("font.png", 8, 8, 6, 6); 47 | EngineInit(); 48 | 49 | // clear canvas so transition starts on a blank screen 50 | mainCanvasContext.fillStyle='#c7f0d8'; 51 | mainCanvasContext.fillRect(0,0,mainCanvasSize.x, mainCanvasSize.y); 52 | 53 | Reset(); 54 | EngineUpdate(); 55 | } 56 | 57 | function Reset() 58 | { 59 | // load local storage 60 | /*if (localStorage.kbap_coins) 61 | playerData.coins = parseInt(localStorage.kbap_coins, 10); 62 | if (localStorage.kbap_warp) 63 | warpLevel = parseInt(localStorage.kbap_warp, 10); 64 | if (localStorage.kbap_bestTime) 65 | speedRunBestTime = parseInt(localStorage.kbap_bestTime, 10);*/ 66 | levelNumber = 0; 67 | playerLives = 3; 68 | gameMode = 1; 69 | playerScore = 0; 70 | newHighScore = 0; 71 | levelEggHealth = 30; 72 | levelStartSeed = Date.now(); 73 | playerGhosts = []; 74 | NextLevel(); 75 | } 76 | 77 | function NextLevel() 78 | { 79 | // go to next level 80 | levelNumber++; 81 | 82 | InitLevel(); 83 | } 84 | 85 | let firstRun = 1; 86 | function InitLevel() 87 | { 88 | // reset level stuff 89 | levelTimer.Set(); 90 | cameraScale=2; 91 | levelFrame = 0; 92 | egg = 0; 93 | eggTimer.Set(1); 94 | 95 | // clear everything 96 | if (!firstRun) 97 | StartTransiton(); 98 | firstRun = 0; 99 | ClearGameObjects(); 100 | 101 | RestartLevel(); 102 | 103 | if (gameMode==0) 104 | { 105 | // create the level and player 106 | let startPos = new Vector2(1.75,2); 107 | player = new Player(startPos); 108 | currentPlayerGhost = []; 109 | 110 | let ghostID = 0; 111 | for(let ghost of playerGhosts) 112 | new PlayerGhost(startPos, ghost); 113 | } 114 | else 115 | player = 0; 116 | 117 | // camera is always centered on player 118 | cameraPos = new Vector2(4,2); 119 | } 120 | 121 | /////////////////////////////////////////////////////////////////////////////// 122 | // update/render 123 | 124 | function UpdateFrame() 125 | { 126 | UpdateAudio(); 127 | 128 | // restart if dead or won 129 | if (player && player.deadTimer.IsSet() && player.deadTimer > 3) 130 | loadNextLevel = 2; 131 | /*else if (player && player.IsDead() && !playerLives) 132 | { 133 | let shootButton = KeyWasPressed(32) || MouseWasPressed() || gamepadButtonWasPressed[0]; 134 | if (shootButton) 135 | loadNextLevel = 3; 136 | }*/ 137 | 138 | let userClicked = KeyWasPressed(32) || MouseWasPressed() || gamepadButtonWasPressed[0]; 139 | // debug key N to load next level 140 | if (debug && KeyWasPressed(78)) 141 | loadNextLevel = 1; 142 | 143 | if (gameMode == 0) 144 | { 145 | if (!loadNextLevel && !manualPaused && (gamepadButtonWasPressed[9] || KeyWasPressed(27))) 146 | { 147 | keyInputData[27].isDown = 0; 148 | manualPaused = 1; 149 | } 150 | 151 | UpdateLevel(); 152 | } 153 | 154 | if (gameMode == 2 && (levelTimer > 2 || levelTimer > .5 && userClicked)) 155 | { 156 | gameMode = 0; 157 | InitLevel(); 158 | } 159 | 160 | if (KeyWasPressed(82)) 161 | { 162 | PlaySound(3); 163 | Reset(); 164 | return; 165 | } 166 | 167 | if (gameMode == 3 && levelTimer > 3) 168 | { 169 | Reset(); 170 | return; 171 | } 172 | 173 | if (gameMode == 1 && levelTimer > .5 && userClicked) 174 | { 175 | PlaySound(4); 176 | gameMode = 2; 177 | InitLevel(); 178 | } 179 | } 180 | 181 | function PreRender() 182 | { 183 | // camera is always centered on player 184 | //cameraPos.Copy(player.pos); 185 | 186 | // clear canvas 187 | mainCanvas.width|=0; 188 | 189 | // draw waves and terrain 190 | let x = mainCanvasContext; 191 | 192 | if (gameMode == 0 || gameMode == 1) 193 | { 194 | x.fillStyle='#43523d'; 195 | x.fillRect(0,0,2e3,2e3); 196 | 197 | x.fillStyle='#c7f0d8'; 198 | // stars 199 | for(let i=40;i--;) 200 | x.fillRect(84-((Math.sin(i**3)*999+(i+10)*levelTimer)|0)%199,22+24*Math.sin(i*i)|0,1,1); 201 | 202 | let speed = Lerp(GetDifficulty(), 1, 4); 203 | 204 | // ground 205 | for(let i=42;i--;) 206 | x.fillRect(i*2,45 + Math.sin(i/2+.5*levelTimer*speed) + Math.sin(i/3+.5*levelTimer*speed)|0,1,9); 207 | 208 | for(let i=42;i--;) 209 | x.fillRect(i*2,47 + Math.sin(i/4+2*levelTimer*speed) + Math.sin(i/5+2*levelTimer*speed)|0,2,9); 210 | } 211 | else if (gameMode == 2 || gameMode == 3) 212 | { 213 | x.fillStyle='#c7f0d8'; 214 | x.fillRect(0,0,2e3,2e3); 215 | } 216 | /*if (1) 217 | { 218 | x.font='12px"' 219 | x.fillStyle='red'; 220 | x.fillText((mousePosWorld.x|0)+' '+(mousePosWorld.y|0),1,10); 221 | x.fillRect(mousePos.x,mousePos.y,1,1); 222 | DrawTile(mousePosWorld,new Vector2(.5,.5),0,0); 223 | }*/ 224 | // draw the level (bottom layer) 225 | //level.Render(); 226 | } 227 | 228 | function PostRender() 229 | { 230 | if (gameMode == 0) 231 | { 232 | DrawText(playerScore,1,1,1,0); 233 | } 234 | if (gameMode == 1) 235 | { 236 | if (localStorage.highScore) 237 | DrawText('High:'+localStorage.highScore,42,Min(-6+levelTimer*4,1)|0,1,2); 238 | 239 | let t = levelTimer*40|0; 240 | let X; 241 | 242 | let w = levelTimer > 2.25 ? 2 : 0 243 | X = t-42; 244 | X=Min(X,42); 245 | DrawText('-EGG TIME-',X,11, 3,2,w,8,7); 246 | X = 42*3-t; 247 | X=Max(X,42); 248 | DrawText('-REWIND-',X,19, 3,2,w,8,7); 249 | 250 | let Y = 96-levelTimer*20|0; 251 | Y = Max(Y,36) 252 | //DrawText('press start',42,Y|0, 2,2,0); 253 | 254 | DrawTile(cameraPos.Clone().AddXY(1+Min(levelTimer/2-6,-2.2),.7+Math.sin(levelTimer)/6), new Vector2(.5,.5),0,0); 255 | 256 | let angle = (levelTimer*2|0)*Math.PI/2; 257 | DrawTile(cameraPos.Clone().AddXY(.5+Math.sin(levelTimer)/2,Max(2-levelTimer/6,.75)), new Vector2(1,1),3,1,angle,0,0,16); 258 | } 259 | else if (gameMode == 2) 260 | { 261 | DrawTile(new Vector2(3.25,1.5),new Vector2(1,1),3,0,0,0,0,16); 262 | DrawText(' x ' + playerLives,42,14, 5,2); 263 | } 264 | else if (gameMode == 3) 265 | { 266 | DrawText('game over',42,14, 5,2,0); 267 | } 268 | 269 | if (gameMode == 2 || gameMode == 3) 270 | { 271 | if (newHighScore && gameMode == 3) 272 | DrawText('new high',42,25, 6,2); 273 | else 274 | DrawText('score',42,25, 6,2); 275 | DrawText(playerScore,42,32, 6,2); 276 | } 277 | 278 | UpdateTransiton(); 279 | 280 | if (paused && gameMode == 0) 281 | DrawText('-paused-',42,20, 3,2,0,8,8); 282 | 283 | if (loadNextLevel) 284 | { 285 | // hook to load next level is here so transition effects work! 286 | if (loadNextLevel==2) 287 | { 288 | // player died 289 | gameMode = playerLives?2:3; 290 | InitLevel(); 291 | } 292 | else 293 | NextLevel(); 294 | loadNextLevel = 0; 295 | } 296 | } 297 | 298 | /////////////////////////////////////////////////////////////////////////////// 299 | // game objects 300 | 301 | class MyGameObject extends GameObject 302 | { 303 | constructor(pos,tileX=0,tileY=0,size=.5,collisionSize=0,health=1) 304 | { 305 | super(pos,tileX,tileY,size,collisionSize,health); 306 | this.damping = 1; 307 | } 308 | 309 | HitEffect(scale=1) 310 | { 311 | let s = scale*this.size.x; 312 | let p = new ParticleEmitter 313 | ( 314 | this.pos, s*.5, s*.2, // pos, emitter size, particle size 315 | color1, color1 316 | ); 317 | } 318 | 319 | Kill() 320 | { 321 | this.HitEffect(); 322 | super.Kill(); 323 | } 324 | } 325 | let lastFrame=-1; 326 | /////////////////////////////////////////////////////////////////////////////// 327 | 328 | class Player extends MyGameObject 329 | { 330 | constructor(pos) 331 | { 332 | super(pos,0,0,.5,2/16,1); 333 | this.inputTimer = new Timer(); 334 | this.playerDamageTimer = new Timer(); 335 | this.shootTimer = new Timer(); 336 | this.shootTimer.Set(1); 337 | this.deadTimer = new Timer(); 338 | this.inputTimer.Set(); 339 | this.renderOrder = 1; 340 | this.charge = 0; 341 | this.damping = .6; 342 | this.isPlayer = 1; 343 | this.hasShot = 0; 344 | } 345 | 346 | Update() 347 | { 348 | // keep player data updated 349 | if (this.IsDead()) 350 | { 351 | // stop and do no more 352 | return; 353 | } 354 | 355 | let shootButton = KeyIsDown(32) || mouseIsDown || gamepadButtonIsDown[0]; 356 | let shot = 0; 357 | if (shootButton) 358 | { 359 | if (this.shootTimer.Elapsed()) 360 | { 361 | if (!this.hasShot) 362 | { 363 | this.HitEffect(2); 364 | this.hasShot = 1; 365 | this.shootTimer.Set(.3); 366 | PlaySound(6); 367 | } 368 | else 369 | { 370 | let big = this.charge==1 371 | shot = big? 2: 1; 372 | PlaySound(big?9:0); 373 | let b = new Bullet(this.pos.Clone().AddXY(4/16,1/16), new Vector2(1,0), big); 374 | this.charge = 0; 375 | this.shootTimer.Set(big?.3:.1); 376 | } 377 | } 378 | } 379 | else if (this.hasShot) 380 | this.charge = Clamp(this.charge+.02,0,1) 381 | 382 | // move input 383 | let acceleration = new Vector2(); 384 | let analogControl = 0; 385 | if (mouseMode) 386 | { 387 | acceleration = mousePosWorld.Clone().Subtract(this.pos).Multiply(2); 388 | acceleration = acceleration.ClampLength(1); 389 | //let m = .02; 390 | //if (acceleration.x>-m && acceleration.x-m && acceleration.y.25 && frame%2) 445 | DrawTile(this.pos.Clone().AddXY(3/8,1/16),new Vector2(.5,.5),this.charge<1?0:1,6); 446 | 447 | if (!this.hasShot) 448 | this.tileX = 2; 449 | 450 | super.Render(); 451 | } 452 | 453 | Damage(damage) 454 | { 455 | // extra long damage timer for player 456 | if (!this.playerDamageTimer.Elapsed()) 457 | return 0; 458 | 459 | // prepvent damage during intro/outro 460 | if (godMode) 461 | return 0; 462 | 463 | // try to apply damage 464 | let damageDone = super.Damage(damage); 465 | if (!damageDone) 466 | return 0; 467 | 468 | PlaySound(3); 469 | 470 | this.HitEffect(); 471 | this.playerDamageTimer.Set(1); 472 | return damageDone; 473 | } 474 | 475 | Kill() 476 | { 477 | if (this.deadTimer.IsSet()) 478 | return; 479 | 480 | playerLives--; 481 | 482 | playerGhosts.push(currentPlayerGhost); 483 | this.deadTimer.Set(); 484 | this.HitEffect(3); 485 | } 486 | } 487 | 488 | /////////////////////////////////////////////////////////////////////////////// 489 | class PlayerGhost extends MyGameObject 490 | { 491 | constructor(pos, ghostData) 492 | { 493 | super(pos,0,0,.5,0); 494 | this.ghostData = ghostData; 495 | this.hasShot = 0; 496 | } 497 | 498 | Update() 499 | { 500 | if (player.IsDead()) 501 | { 502 | this.Kill(); 503 | return; 504 | } 505 | 506 | let ghostData = this.ghostData[levelFrame]; 507 | if (ghostData) 508 | { 509 | this.pos.x = ghostData.x; 510 | this.pos.y = ghostData.y; 511 | 512 | if (ghostData.shot) 513 | { 514 | this.hasShot = 1; 515 | new Bullet(this.pos.Clone().AddXY(4/16,1/16), new Vector2(1,0), ghostData.shot==2); 516 | } 517 | } 518 | else 519 | this.Kill(); 520 | } 521 | 522 | Render() 523 | { 524 | if (frame%2) 525 | return; 526 | // figure out the tile, rotation and mirror 527 | this.tileX = (frame/2|0)%2; 528 | this.tileY = 0; 529 | 530 | if (!this.hasShot) 531 | this.tileX = 2; 532 | 533 | super.Render(); 534 | } 535 | 536 | Kill() 537 | { 538 | this.HitEffect(.5); 539 | super.Kill(); 540 | } 541 | 542 | } 543 | /////////////////////////////////////////////////////////////////////////////// 544 | 545 | class Bullet extends MyGameObject 546 | { 547 | constructor(pos, direction, isBig=0, team=0) 548 | { 549 | super(pos,isBig?1:0,6,.5,isBig?1/16:1/32); 550 | 551 | this.team = team; 552 | this.isBig = isBig; 553 | this.throwFrames = 8; 554 | 555 | let speed = team==0? .2: .03; 556 | this.velocity = direction.Clone().Normalize(speed); 557 | //this.velocity.Add(owner.velocity.Clone()); 558 | 559 | this.hitObjects=[]; 560 | 561 | if (this.team == 1) 562 | this.tileX = 2; 563 | 564 | this.RenderOrder = this.team? -10 : 10; 565 | } 566 | 567 | CollideLevel(data, pos) 568 | { 569 | return super.CollideLevel(data, pos); 570 | } 571 | 572 | Update() 573 | { 574 | if (this.pos.x>cameraPos.x+3 || this.pos.x 3) 575 | { 576 | // kill if offscreen 577 | this.Destroy(); 578 | return; 579 | } 580 | 581 | gameObjects.forEach(o=> 582 | { 583 | let isEnemy = (this.team == 0 && o.isEnemy) || (this.team == 1 && o.isPlayer); 584 | 585 | if (isEnemy && o.IsTouching(this) && !this.hitObjects.includes(o)) 586 | { 587 | this.hitObjects.push(o); 588 | if (o.Damage(this.isBig?4:1, this)) 589 | { 590 | // apply damage 591 | if (o.canBeDamaged) 592 | o.velocity.Add(this.velocity.Clone(this.isBig?.4:.1)); 593 | this.damageTimer.Set(); 594 | if (!this.isBig) 595 | { 596 | this.Destroy(); 597 | return; 598 | } 599 | } 600 | } 601 | }); 602 | 603 | super.Update(); 604 | } 605 | } 606 | 607 | /////////////////////////////////////////////////////////////////////////////// 608 | 609 | class Pickup extends MyGameObject 610 | { 611 | constructor(pos, type=0) 612 | { 613 | super(pos,2+type,5,.5,.2); 614 | this.type = type; 615 | } 616 | 617 | Update() 618 | { 619 | // let player pick it up 620 | if (!player.IsDead() && player.IsTouching(this)) 621 | this.Pickup(); 622 | 623 | super.Update(); 624 | } 625 | 626 | Pickup() 627 | { 628 | 629 | this.Destroy(); 630 | } 631 | } 632 | 633 | /////////////////////////////////////////////////////////////////////////////// 634 | 635 | class Enemy extends MyGameObject 636 | { 637 | constructor(pos,tileX=0,tileY=0,size=.25,collisionSize=0,health=1) 638 | { 639 | super(pos,tileX,tileY,size,collisionSize,health); 640 | this.isEnemy = 1; 641 | this.damping = .9; 642 | } 643 | 644 | Damage(damage) 645 | { 646 | let damageDone = super.Damage(damage); 647 | if (damageDone && !this.IsDead()) 648 | { 649 | this.HitEffect(.5); 650 | } 651 | 652 | return damageDone; 653 | } 654 | 655 | Update() 656 | { 657 | if (player.IsTouching(this)) 658 | if (player.Damage(1)) 659 | { 660 | // push player when damaged 661 | //let accel = player.pos.Clone(); 662 | //accel.Subtract(this.pos).Normalize(.1); 663 | //player.velocity.Add(accel); 664 | } 665 | 666 | super.Update(); 667 | } 668 | } 669 | 670 | /////////////////////////////////////////////////////////////////////////////// 671 | 672 | class BigEgg extends Enemy 673 | { 674 | constructor(pos) 675 | { 676 | super(pos,3,1,1,.45,levelEggHealth); 677 | this.damping = .95; 678 | this.renderOrder = -1; 679 | } 680 | 681 | Update() 682 | { 683 | if (levelFrame%(10+50*(this.health/this.healthMax|0))==0) 684 | this.angle += PI/2; 685 | 686 | this.pos.x = Min(this.pos.x, cameraPos.x + 3) 687 | 688 | if (this.pos.y < cameraPos.y - 1.5) 689 | this.velocity.AddXY(0,.05); 690 | if (this.pos.y > cameraPos.y + 1.5) 691 | this.velocity.AddXY(0,-.05); 692 | 693 | if (this.pos.x .5 && LevelRandom() < .1*GetDifficulty()) 750 | { 751 | // extra randomness 752 | type = LevelRandom()*5|0; 753 | } 754 | 755 | let health = 4; 756 | if (type == 3) 757 | health = 1e3; 758 | super(pos,0,1+type,.5,.2, health); 759 | this.type = type; 760 | this.moveOffset = ++enemyMoveOffset; 761 | this.shootTimer = new Timer(); 762 | this.shootTimer.Set((enemyMoveOffset%8)/4); 763 | this.mode = 0; 764 | 765 | if (this.type == 3) 766 | this.canBeDamaged = 0; 767 | } 768 | 769 | Update() 770 | { 771 | if (this.pos.xcameraPos.x+5) 772 | { 773 | // kill if offscreen 774 | this.Destroy(); 775 | return; 776 | } 777 | 778 | let speed = levelSpeed*Lerp(GetDifficulty(), 1, 2); 779 | if (this.type != 2 || player.IsDead()) 780 | this.pos.AddXY(-speed,0); 781 | 782 | if (this.type == 0) // ufo 783 | { 784 | if (this.mode == 0 && this.pos.x < cameraPos.x-2.4) 785 | this.mode = 1; 786 | 787 | if (this.mode == 1) 788 | this.pos.AddXY(3*speed,0); 789 | 790 | this.tileX = ((frame+enemyMoveOffset)/36|0)%3; 791 | } 792 | else if (this.type == 1) // squid 793 | { 794 | let accel = new Vector2(); 795 | accel.y = Math.sin(this.lifeTimer*2 + this.moveOffset )*.002; 796 | this.velocity.Add(accel); 797 | 798 | this.tileX = ((frame+enemyMoveOffset)/36|0)%3; 799 | } 800 | else if (this.type == 2) // ghost 801 | { 802 | if (!player.IsDead()) 803 | { 804 | let accel = player.pos.Clone().Subtract(this.pos); 805 | accel.Normalize(.004); 806 | this.velocity.Add(accel); 807 | } 808 | this.tileX = ((frame+enemyMoveOffset)/10|0)%4; 809 | } 810 | else if (this.type == 3) // blocker 811 | { 812 | //this.velocity.AddXY(-move,0); 813 | this.collisionSize = .25; 814 | } 815 | else if (this.type == 4) // shooter 816 | { 817 | if (this.shootTimer.Elapsed()) 818 | { 819 | let direction = player.pos.Clone().Subtract(this.pos).Normalize(); 820 | 821 | let b = new Bullet(this.pos, direction, 1, 1); 822 | PlaySound(8); 823 | } 824 | 825 | if (!this.shootTimer.IsSet() || this.shootTimer.Elapsed()) 826 | this.shootTimer.Set(2); 827 | 828 | this.tileX = this.shootTimer.IsSet() && this.shootTimer > -.5? 1:0; 829 | } 830 | 831 | super.Update(); 832 | } 833 | 834 | Kill() 835 | { 836 | PlaySound(6); 837 | AddToScore(1); 838 | super.Kill(); 839 | } 840 | 841 | Damage(damage) 842 | { 843 | PlaySound(this.canBeDamaged?1:5); 844 | return super.Damage(damage); 845 | } 846 | } 847 | 848 | 849 | /////////////////////////////////////////////////////////////////////////////// 850 | // level builder 851 | 852 | let levelStartSeed = Date.now(); 853 | let levelSeed = 0; 854 | let levelSpawnTimer=new Timer(); 855 | let levelEnemyType=0; 856 | let enemyMoveOffset = 0; 857 | let levelSpeed = .02; 858 | 859 | function GetDifficulty() 860 | { 861 | let p = levelTimer / 60; 862 | return Clamp(p, 0, 1); 863 | } 864 | 865 | // random seeded float 866 | let LevelRandom=_=> 867 | { 868 | levelSeed^=levelSeed<<13; 869 | levelSeed^=levelSeed>>7; 870 | levelSeed^=levelSeed<<17; 871 | return Math.abs(levelSeed)%(1e9-1)/1e9; 872 | } 873 | 874 | function RestartLevel() 875 | { 876 | enemyMoveOffset = 0; 877 | levelSeed = levelStartSeed; 878 | levelEnemyType = -1; 879 | levelSpawnTimer.Set(); 880 | } 881 | 882 | function SpawnEnemies(type) 883 | { 884 | if (!spawnEnemies) 885 | return; 886 | 887 | if (type < 0) 888 | { 889 | levelSpawnTimer.Set(3); 890 | return; 891 | } 892 | 893 | // spawn more enemies 894 | let spawnPos = cameraPos.Clone(); 895 | spawnPos.AddXY(3,0) 896 | 897 | let nextSpawnTime = 0; 898 | 899 | if (levelEnemyType == 0) // ufo 900 | { 901 | spawnPos.AddXY(0,-2); 902 | spawnPos.AddXY(0,LevelRandom()*.5-.25); 903 | for(let i=3;i--;) 904 | new BasicEnemy(spawnPos.AddXY(0,1), 0); 905 | nextSpawnTime = 1; 906 | } 907 | else if (levelEnemyType == 1) // squid 908 | { 909 | spawnPos.AddXY(-1,0); 910 | let count = 3+LevelRandom()*2|0; 911 | for(let i=count;i--;) 912 | new BasicEnemy(spawnPos.AddXY(1,0), 1); 913 | nextSpawnTime = count-.5; 914 | } 915 | else if (levelEnemyType == 2) // ghost 916 | { 917 | spawnPos.AddXY(0,LevelRandom()*2-1); 918 | new BasicEnemy(spawnPos, 2); 919 | nextSpawnTime = .5; 920 | } 921 | else if (levelEnemyType == 3) // block 922 | { 923 | let subtype = LevelRandom()*3|0; 924 | spawnPos.AddXY(0,subtype<2?-2:-.5); 925 | let count = 4; 926 | if (subtype==1) 927 | count = 5, spawnPos.AddXY(0,-.25); 928 | for(let i=count;i--;) 929 | { 930 | new BasicEnemy(spawnPos.AddXY(0,.5), 3); 931 | if (subtype == 1 && i == 2) 932 | spawnPos.AddXY(0,1) 933 | } 934 | nextSpawnTime = 1; 935 | } 936 | else if (levelEnemyType == 4) // shooter 937 | { 938 | spawnPos.AddXY(0,LevelRandom()*2-1); 939 | new BasicEnemy(spawnPos, 4); 940 | nextSpawnTime = .5; 941 | } 942 | 943 | let extraTime = Lerp(GetDifficulty(), 2 - LevelRandom()*.5, 0) 944 | levelSpawnTimer.Set(nextSpawnTime + extraTime); 945 | } 946 | 947 | function UpdateLevel() 948 | { 949 | ++levelFrame; 950 | 951 | if (levelSpawnTimer.Elapsed()) 952 | { 953 | SpawnEnemies(levelEnemyType); 954 | levelEnemyType = LevelRandom()*5|0; 955 | } 956 | 957 | if (!egg && eggTimer.Elapsed()) 958 | egg = new BigEgg(cameraPos.Clone().AddXY(3,LevelRandom()*2-1)); 959 | } 960 | 961 | function AddToScore(score) 962 | { 963 | /*if ((playerScore/50|0) != ((playerScore+score)/50|0)) 964 | { 965 | // extra life 966 | playerLives++; 967 | }*/ 968 | 969 | playerScore += score; 970 | if (!localStorage.highScore || playerScore > localStorage.highScore) 971 | { 972 | localStorage.highScore = playerScore; 973 | newHighScore = 1; 974 | } 975 | } 976 | 977 | /////////////////////////////////////////////////////////////////////////////// 978 | // level transition system 979 | 980 | let transitionTimer = new Timer(); 981 | let transitionCanvasContext = transitionCanvas.getContext('2d'); 982 | 983 | function StartTransiton() 984 | { 985 | // copy main canvas to transition canvas 986 | transitionTimer.Set(); 987 | transitionCanvas.width = mainCanvasSize.x; 988 | transitionCanvas.height = mainCanvasSize.y; 989 | transitionCanvasContext.drawImage(mainCanvas,0,0); 990 | } 991 | 992 | function UpdateTransiton() 993 | { 994 | let transitionTime = transitionTimer.Get(); 995 | if (transitionTime > .5 || transitionTime < 0) 996 | return; 997 | 998 | let p = transitionTime/.5; 999 | let w = mainCanvas.width; 1000 | let h = mainCanvas.height; 1001 | 1002 | //mainCanvasContext.drawImage(transitionCanvas,0,p*99); 1003 | 1004 | for(let j=8;j--;) 1005 | { 1006 | let dWidth = w; 1007 | let dHeight = 8*(1-p)|0; 1008 | let sx = 0; 1009 | let sy = j*8+(8-dHeight); 1010 | let x = 0; 1011 | let y = sy; 1012 | 1013 | mainCanvasContext.drawImage(transitionCanvas,0,sy,w,dHeight,0,sy,w,dHeight); 1014 | } 1015 | } 1016 | 1017 | /////////////////////////////////////////////////////////////////////////////// 1018 | // ZzFXmicro - Zuper Zmall Zound Zynth - MIT License - Copyright 2019 Frank Force 1019 | let zzfx_v=.15; 1020 | let zzfx_x=0; 1021 | let SquareWave=v=>Math.cos(v)>0?1:-1 1022 | let zzfx=(e,f,a,b=1,d=.1,g=0,h=0,k=0,l=0)=>{if(!zzfx_x)return;let S=44100;a*=2*PI/S;a*=1+RandBetween(-f,f);g*=1E3*PI/(S**2);b=S*b|0;d=d*b|0;k*=2*PI/S;l*=PI;f=[];for(let m=0,n=0,c=0;cInit(); 1145 | tileImage.src = 'tiles.png?1'; --------------------------------------------------------------------------------