├── example ├── flappy │ ├── images │ │ ├── bg.png │ │ ├── ground.png │ │ └── flappy_packer.png │ ├── index.html │ └── scripts │ │ └── flappy.js └── HelloWorld.html ├── README.md └── src └── Game.js /example/flappy/images/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/finscn/The-Best-JS-Game-Framework/HEAD/example/flappy/images/bg.png -------------------------------------------------------------------------------- /example/flappy/images/ground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/finscn/The-Best-JS-Game-Framework/HEAD/example/flappy/images/ground.png -------------------------------------------------------------------------------- /example/flappy/images/flappy_packer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/finscn/The-Best-JS-Game-Framework/HEAD/example/flappy/images/flappy_packer.png -------------------------------------------------------------------------------- /example/flappy/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |press P to pause, R to resume.136 | 137 | 138 | 139 | -------------------------------------------------------------------------------- /src/Game.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | (function(exports, undefined) { 4 | 5 | var ns = exports.Best = exports.Best || {}; 6 | /***************************/ 7 | /********** Game **********/ 8 | /***************************/ 9 | 10 | var Game = ns.Game = function(options) { 11 | for (var p in options) { 12 | this[p] = options[p] 13 | } 14 | }; 15 | 16 | Game.prototype = { 17 | constructor: Game, 18 | 19 | id: null, 20 | width: 600, 21 | height: 400, 22 | FPS: 60, 23 | caption: '', 24 | context: null, // graphic-context 25 | 26 | init: function() { 27 | var Me = this; 28 | this._run = function() { 29 | Me.run(); 30 | }; 31 | this.timer = { 32 | now: 0, 33 | last: 0, 34 | step: Math.round(1000 / this.FPS) 35 | }; 36 | 37 | this.initGraphicContext(); 38 | 39 | if (this.onInit) { 40 | this.onInit.apply(this, arguments); 41 | } 42 | }, 43 | 44 | // implement by yourself 45 | initGraphicContext: function() { 46 | 47 | }, 48 | 49 | start: function() { 50 | this.timer.now = Date.now(); 51 | this.timer.last = Date.now(); 52 | this.paused = false; 53 | this.running = true; 54 | if (this.onStart) { 55 | this.onStart(); 56 | } 57 | this.run(); 58 | }, 59 | stop: function() { 60 | this.running = false; 61 | if (this.onStop) { 62 | this.onStop(); 63 | } 64 | }, 65 | pause: function() { 66 | this.paused = true; 67 | if (this.onPause) { 68 | this.onPause(); 69 | } 70 | }, 71 | resume: function() { 72 | this.paused = false; 73 | if (this.onResume) { 74 | this.onResume(); 75 | } 76 | }, 77 | 78 | pauseLoop: function() { 79 | this.running = false; 80 | if (this.loopId) { 81 | clearTimeout(this.loopId); 82 | } 83 | }, 84 | resumeLoop: function() { 85 | this.timer.last = Date.now(); 86 | this.run(); 87 | }, 88 | 89 | run: function() { 90 | var now = this.timer.now = Date.now(); 91 | var timeStep = now - this.timer.last; 92 | this.timer.last = now; 93 | this.loopId = setTimeout(this._run, this.timer.step); 94 | this.handleInput(timeStep, now); 95 | if (!this.paused && timeStep > 1) { 96 | this.update(timeStep, now); 97 | this.render(timeStep, now); 98 | } 99 | if (!this.running && this.loopId) { 100 | clearTimeout(this.loopId); 101 | } 102 | }, 103 | 104 | setScene: function(scene) { 105 | this.scene = scene; 106 | scene.enter(); 107 | }, 108 | 109 | update: function(timeStep, now) { 110 | if (this.scene) { 111 | this.scene.update(timeStep, now); 112 | } 113 | }, 114 | 115 | render: function(timeStep, now) { 116 | if (this.scene) { 117 | this.scene.render(this.context, timeStep, now); 118 | } 119 | }, 120 | 121 | handleInput: function(timeStep, now) { 122 | if (this.scene) { 123 | this.scene.handleInput(timeStep, now); 124 | } 125 | }, 126 | // some hooks, implement by yourself 127 | onInit: null, 128 | onStart: null, 129 | onPause: null, 130 | onResume: null, 131 | onStop: null, 132 | 133 | }; 134 | 135 | 136 | /***************************/ 137 | /********** Scene **********/ 138 | /***************************/ 139 | 140 | var Scene = ns.Scene = function(options) { 141 | for (var p in options) { 142 | this[p] = options[p] 143 | } 144 | }; 145 | 146 | Scene.prototype = { 147 | constructor: Scene, 148 | id: null, 149 | init: function(game) { 150 | 151 | }, 152 | enter: function() { 153 | 154 | }, 155 | leave: function(nextScene) { 156 | 157 | }, 158 | update: function(timeStep, now) { 159 | 160 | }, 161 | render: function(context, timeStep, now) { 162 | 163 | }, 164 | handleInput: function(timeStep, now) { 165 | 166 | } 167 | }; 168 | 169 | ns.extend = function(prototype, superclass) { 170 | var subclass = function(options) { 171 | for (var p in options) { 172 | this[p] = options[p]; 173 | } 174 | }; 175 | var cp = subclass.prototype, 176 | sp; 177 | if (superclass) { 178 | sp = superclass.prototype; 179 | for (var p in sp) { 180 | cp[p] = sp[p]; 181 | } 182 | } 183 | for (var p in prototype) { 184 | cp[p] = prototype[p]; 185 | } 186 | cp.constructor = subclass; 187 | subclass.$super = sp; 188 | subclass.superclass = superclass || null; 189 | return subclass; 190 | } 191 | 192 | function _extend(prototype) { 193 | var subclass = ns.extend(prototype, this); 194 | subclass.extend = this.extend; 195 | return subclass; 196 | }; 197 | 198 | Game.extend = _extend; 199 | Scene.extend = _extend; 200 | 201 | })(this); 202 | -------------------------------------------------------------------------------- /example/flappy/scripts/flappy.js: -------------------------------------------------------------------------------- 1 | void function(exports) { 2 | /** 3 | * 扑腾鸟 power by The-Best-JS-Game-Framework 4 | * @author 王集鹄(wangjihu,http//weibo.com/zswang) 5 | * @version 2014年05月26日 6 | */ 7 | 8 | var DEBUG = false; 9 | 10 | var groundInfo = { 11 | height: 281 12 | }; 13 | 14 | var bridInfo = { // 鸟在偏移信息 15 | gravity: 2.5 / 1000, // 重力加速度 16 | jumpSpeed: -800 / 1000, // 初速度 17 | minAngle: -0.1 * Math.PI, 18 | maxAngle: 0.5 * Math.PI, 19 | top: 620, 20 | left: 175, 21 | width: 86, 22 | height: 60, 23 | offsets: [[673, 1], [673, 62], [673, 123]] 24 | }; 25 | 26 | var readyTextInfo = { // Get Ready! 偏移信息 27 | top: 340, 28 | width: 508, 29 | height: 158, 30 | offset: [510,300] 31 | }; 32 | 33 | var clockInfo = { 34 | top: 575, 35 | width: 286, 36 | height: 246, 37 | offset: [760, 1] 38 | }; 39 | 40 | var overTextInfo = { 41 | top: 270, 42 | width: 508, 43 | height: 158, 44 | offset: [1, 300] 45 | }; 46 | 47 | var baseInfo = { 48 | top: 460, 49 | width: 590, 50 | height: 298, 51 | offset: [1, 1] 52 | }; 53 | 54 | var startInfo = { // 开始按钮 55 | top: 846, 56 | left: 68, 57 | width: 290, 58 | height: 176, 59 | offset: [448, 459] 60 | }; 61 | 62 | var gradeInfo = { // 排名 63 | top: 846, 64 | left: 368, 65 | width: 290, 66 | height: 176, 67 | offset: [1556, 1] 68 | }; 69 | 70 | var grayInfo = { 71 | left: 136, 72 | top: 568, 73 | width: 123, 74 | height: 113, 75 | offset: [1047,160] 76 | }; 77 | 78 | var yellowInfo = { 79 | left: 136, 80 | top: 568, 81 | width: 123, 82 | height: 113, 83 | offset: [1847, 85] 84 | }; 85 | 86 | var numberInfo = { 87 | height: 91, 88 | itemWidth: 60, 89 | itemOffset: 70, 90 | top: 0, 91 | zoom: 1, 92 | align: 'center', 93 | offset: [1019, 300] 94 | }; 95 | 96 | var newInfo = { 97 | left: 420, 98 | top: 610, 99 | width: 81, 100 | height: 36, 101 | offset: [673,184] 102 | }; 103 | 104 | var holdbackInfo = { 105 | space: 255, // 缝隙大小 106 | distance: 148 + 280, // 两个之间的距离 107 | minTop: 80, // 最小高度 108 | beginTick: 2000, // 出现时机 109 | width: 148, 110 | height: 830, 111 | offsets: [[1, 459], [150, 459], [299, 459]] 112 | }; 113 | 114 | /** 115 | * 计算抛物线 116 | * @param{Number} v0 初速度 117 | * @param{Number} t 时间 118 | * @param{Number} a 加速度 119 | */ 120 | function parabola(v0, a, t) { 121 | return v0 * t + 0.5 * a * t * t; 122 | } 123 | 124 | function ptInRect(p, rect) { 125 | return p[0] >= rect.left && p[0] <= rect.left + rect.width && 126 | p[1] >= rect.top && p[1] <= rect.top + rect.height; 127 | } 128 | 129 | var context; 130 | var jumpTick; 131 | var jumpTop; 132 | var canvas; 133 | 134 | var score = 0; // 积分 135 | var best = 0; // 历史最高 136 | if (window.localStorage) { 137 | best = localStorage.getItem('best') || 0; 138 | } 139 | var status; // 'loading', 'ready', 'playing', 'gameover' 140 | var statusTick; 141 | var holdbackMax = 1000; 142 | var holdbackList = new Array(holdbackMax); // 障碍物地图 143 | var bestNew = false; // 新记录 144 | function setStatus(value) { 145 | if (status == value) { 146 | return; 147 | } 148 | status = value; 149 | switch (status) { 150 | case 'loading': 151 | statusTick = 0; 152 | context.font = "30px Verdana"; 153 | break; 154 | case 'ready': 155 | statusTick = 0; 156 | bestNew = false; 157 | score = 0; 158 | minHoldbackIndex = 0; 159 | for (var i = 0; i < holdbackMax; i++) { 160 | holdbackList[i] = holdbackInfo.minTop + 161 | Math.random() * (canvas.height - groundInfo.height - 162 | holdbackInfo.minTop * 2 - holdbackInfo.space); 163 | } 164 | break; 165 | case 'playing': 166 | jumpTop = bridInfo.top; 167 | jumpTick = new Date; 168 | break; 169 | case 'gameover': 170 | statusTick = 0; 171 | if (score > best) { 172 | best = score; 173 | bestNew = true; 174 | if (window.localStorage) { 175 | localStorage.setItem('best', best); 176 | } 177 | } 178 | break; 179 | } 180 | } 181 | 182 | var loadCount = 0; 183 | var imageHost = 'images/'; 184 | var images; 185 | 186 | var KeyState = {}; 187 | var Key = { 188 | Space: 32, 189 | Enter: 13, 190 | Up: 38, 191 | P: 80, 192 | R: 82, 193 | }; 194 | 195 | /** 196 | * 渲染小鸟 197 | * @param top 小鸟高度 198 | * @param angle 角度 199 | */ 200 | function renderBrid(top, angle, offset) { 201 | if (typeof offset == 'undefined') { 202 | offset = parseInt(+new Date() / 150) % bridInfo.offsets.length; 203 | } 204 | 205 | if (angle) { 206 | context.save(); 207 | context.translate(bridInfo.left + bridInfo.width / 2, top + bridInfo.height / 2); 208 | context.rotate(angle); 209 | 210 | context.drawImage(images[2], 211 | bridInfo.offsets[offset][0], bridInfo.offsets[offset][1], 212 | bridInfo.width, bridInfo.height, 213 | -bridInfo.width / 2, -bridInfo.height / 2, 214 | bridInfo.width, bridInfo.height); 215 | context.restore(); 216 | } else { 217 | context.drawImage(images[2], 218 | bridInfo.offsets[offset][0], bridInfo.offsets[offset][1], 219 | bridInfo.width, bridInfo.height, 220 | bridInfo.left, top, bridInfo.width, bridInfo.height); 221 | } 222 | } 223 | 224 | function renderBackground() { 225 | context.drawImage(images[0], 0, 0); 226 | } 227 | 228 | function renderGround() { 229 | var offset = status == 'playing' ? 230 | -((statusTick) / 1200 * holdbackInfo.distance) % 840 : 0; 231 | context.save(); 232 | context.translate(offset, canvas.height - groundInfo.height); 233 | context.fillStyle = patternGround; 234 | context.fillRect(-offset, 0, canvas.width, groundInfo.height); 235 | context.restore(); 236 | } 237 | 238 | function renderItem(info, top) { 239 | context.drawImage(images[2], 240 | info.offset[0], info.offset[1], 241 | info.width, info.height, 242 | info.left || (canvas.width - info.width) / 2, (info.top || 0) + (top || 0), 243 | info.width, info.height); 244 | } 245 | 246 | /** 247 | * 绘制障碍物 248 | * @param{Number} left 左边距 249 | * @param{Number} top 缝隙高度 250 | * @param{Number} space 缝隙大小 251 | */ 252 | function renderHoldback(left, top, space) { 253 | space = space || holdbackInfo.space; 254 | 255 | // 绘制上方 256 | context.drawImage(images[2], 257 | holdbackInfo.offsets[1][0], holdbackInfo.offsets[1][1] + (holdbackInfo.height - top), 258 | holdbackInfo.width, top, 259 | left, 0, 260 | holdbackInfo.width, top); 261 | 262 | var height = canvas.height - groundInfo.height - top - space; 263 | // 绘制下方 264 | context.drawImage(images[2], 265 | holdbackInfo.offsets[0][0], holdbackInfo.offsets[0][1], 266 | holdbackInfo.width, height, 267 | left, top + space, 268 | holdbackInfo.width, height); 269 | } 270 | 271 | var loadingText = 'Loading...'; 272 | var loadingTextWidth; 273 | function renderLoading() { 274 | context.save(); 275 | var tick = statusTick; 276 | var gradient = context.createLinearGradient(0, 0, canvas.width, 0); 277 | gradient.addColorStop(0, 'white'); 278 | gradient.addColorStop((tick / 100 % 20 / 20), 'blue'); 279 | context.fillStyle = gradient; 280 | if (!loadingTextWidth) { 281 | var meause = context.measureText(loadingText); 282 | loadingTextWidth = meause.width; 283 | } 284 | context.fillText(loadingText, 285 | (canvas.width - loadingTextWidth) / 2, (canvas.height - 30) / 2); 286 | context.restore(); 287 | } 288 | 289 | /** 290 | * 渲染“Get Ready!” 291 | * @param top 文字高度 292 | */ 293 | function renderReady(top) { 294 | renderItem(readyTextInfo, top); 295 | } 296 | 297 | function renderClock(top) { 298 | renderItem(clockInfo, top); 299 | } 300 | 301 | function renderOver(top) { 302 | renderItem(overTextInfo, top); 303 | } 304 | 305 | function renderBase(top) { 306 | renderItem(baseInfo, top); 307 | } 308 | 309 | function renderStartButton(top) { 310 | renderItem(startInfo, top); 311 | } 312 | 313 | function renderGradeButton(top) { 314 | renderItem(gradeInfo, top); 315 | } 316 | 317 | function renderGray(top) { 318 | renderItem(grayInfo, top); 319 | } 320 | 321 | function renderYellow(top) { 322 | renderItem(yellowInfo, top); 323 | } 324 | 325 | function renderNew(top) { 326 | renderItem(newInfo, top); 327 | } 328 | 329 | /** 330 | * 绘制数字 331 | * @param{Number} number 显示数字 332 | * @param{Number} x 参考坐标 333 | * @param{Number} top 顶部坐标 334 | * @param{Number} zoom 缩放倍数 335 | */ 336 | function renderNumber(number, x, top, zoom, align) { 337 | top = top || numberInfo.top; 338 | zoom = zoom || numberInfo.zoom; 339 | align = align || numberInfo.align; 340 | number = String(parseInt(number)); 341 | 342 | var width = number.length * numberInfo.itemWidth * zoom; 343 | var left; 344 | switch (align) { 345 | case 'center': 346 | left = x - (width / 2); 347 | break; 348 | case 'right': 349 | left = x - width; 350 | break; 351 | default: 352 | left = x; 353 | break; 354 | } 355 | 356 | for (var i = 0; i < number.length; i++) { 357 | var item = parseInt(number.charAt(i)); 358 | var offset = item == 1 ? -10 : (item == 0 ? 0 : -18); 359 | context.drawImage(images[2], 360 | numberInfo.offset[0] + offset + numberInfo.itemOffset * item, 361 | numberInfo.offset[1], 362 | numberInfo.itemWidth, numberInfo.height, 363 | left + numberInfo.itemWidth * i * zoom, top, 364 | numberInfo.itemWidth * zoom, numberInfo.height * zoom); 365 | } 366 | } 367 | 368 | var minHoldbackIndex = 0; 369 | var holdbackOffset = 0; 370 | function renderHoldbacks() { 371 | for (var i = minHoldbackIndex; true; i++) { 372 | if (canvas.width + holdbackOffset + i * holdbackInfo.distance > canvas.width) { 373 | break; 374 | } 375 | if (canvas.width + holdbackOffset + i * holdbackInfo.distance + holdbackInfo.width < 0) { 376 | minHoldbackIndex = i; 377 | } 378 | renderHoldback(canvas.width + holdbackOffset + i * holdbackInfo.distance, 379 | holdbackList[i % holdbackMax]); 380 | } 381 | } 382 | 383 | function cross(a, b, c, d) { 384 | return (a >= c && a <= d) || 385 | (b >= c && b <= d) || 386 | (c >= a && c <= b) || 387 | (d >= a && d <= b); 388 | } 389 | 390 | function checkOver() { 391 | if (bridTop + 0.01 >= canvas.height - bridInfo.height - groundInfo.height) { 392 | return true; 393 | } 394 | 395 | var tick = statusTick - holdbackInfo.beginTick; 396 | if (tick < 0) { 397 | return; 398 | } 399 | var offset = -tick / 1200 * holdbackInfo.distance; 400 | for (var i = minHoldbackIndex; true; i++) { 401 | if (canvas.width + offset + i * holdbackInfo.distance > canvas.width) { 402 | break; 403 | } 404 | var x = canvas.width + offset + i * holdbackInfo.distance; 405 | var y = holdbackList[i % holdbackMax]; 406 | if (cross(bridInfo.left, bridInfo.left + bridInfo.width - 10, 407 | x, x + holdbackInfo.width 408 | )) { 409 | if (bridTop < y) { 410 | return true; 411 | } 412 | if (bridTop + bridInfo.height - 10 > y + holdbackInfo.space) { 413 | return true; 414 | } 415 | } 416 | } 417 | } 418 | 419 | var bridTop; 420 | 421 | function setScore(value) { 422 | if (value == score) { 423 | return; 424 | } 425 | score = value; 426 | //playSound(); 427 | } 428 | 429 | function fly(e) { 430 | switch (status) { 431 | case 'playing': 432 | var tick = new Date - jumpTick; 433 | var bridTop = parabola(bridInfo.jumpSpeed, bridInfo.gravity, tick % 5000); 434 | bridTop = Math.min(canvas.height - bridInfo.height - groundInfo.height, 435 | jumpTop + bridTop); 436 | jumpTop = bridTop; 437 | jumpTick = new Date; 438 | break; 439 | } 440 | } 441 | 442 | /** 443 | * 全局渲染 444 | */ 445 | function render() { 446 | if (status == 'loading') { 447 | context.fillStyle = '#000000'; 448 | context.fillRect(0, 0, canvas.width, canvas.height); 449 | } else { 450 | renderBackground(); 451 | renderGround(); 452 | } 453 | var tick = statusTick; 454 | switch (status) { 455 | case 'loading': 456 | renderLoading(); 457 | break; 458 | case 'ready': 459 | renderNumber(score, canvas.width / 2, 150); 460 | renderClock(); 461 | renderReady(0); 462 | renderBrid(bridInfo.top + 15 * Math.cos((tick / 150) % 100)); 463 | break; 464 | case 'playing': 465 | holdbackOffset = -(tick - holdbackInfo.beginTick) / 1200 * holdbackInfo.distance; 466 | setScore(Math.max(0, (tick - holdbackInfo.beginTick - 100) / 1200)); 467 | tick = new Date - jumpTick; 468 | bridTop = parabola(bridInfo.jumpSpeed, 469 | bridInfo.gravity, tick); 470 | bridTop = Math.min(canvas.height - bridInfo.height - groundInfo.height, 471 | jumpTop + bridTop); 472 | var angle = (bridTop - jumpTop) / 100 + 0.01 * Math.PI; 473 | angle = Math.max(bridInfo.minAngle, 474 | Math.min(bridInfo.maxAngle, angle)); 475 | if (checkOver(bridTop)) { 476 | overAngle = angle; 477 | setStatus('gameover'); 478 | } 479 | 480 | renderHoldbacks(); 481 | renderNumber(score, canvas.width / 2, 150); 482 | renderBrid(bridTop, angle); 483 | break; 484 | case 'gameover': 485 | var angle = (bridTop - jumpTop) / 100 + 0.01 * Math.PI; 486 | angle = Math.max(bridInfo.minAngle, 487 | Math.min(bridInfo.maxAngle, angle)); 488 | renderBrid(bridTop, angle, 0); 489 | renderHoldbacks(); 490 | renderBase(0); 491 | renderOver(0); 492 | renderGray(0); 493 | renderYellow(0); 494 | if (bestNew) { 495 | renderNew(0); 496 | } 497 | renderNumber(score, 580, 545, 0.6, 'right'); 498 | renderNumber(best, 580, 650, 0.6, 'right'); 499 | renderStartButton(0); 500 | renderGradeButton(0); 501 | break; 502 | } 503 | } 504 | 505 | exports.flappy = new Best.Game({ 506 | FPS: 60, 507 | onInit: function(c) { 508 | canvas = c; 509 | context = canvas.getContext("2d"); 510 | context.font = '76px 宋体'; 511 | context.fillStyle = 'red'; 512 | 513 | this.initEvent(); 514 | setStatus('loading'); 515 | images = ['bg.png', 'ground.png', 'flappy_packer.png'].map(function(url) { 516 | var result = new Image(); 517 | result.onload = function(e) { 518 | if (DEBUG) { 519 | console.log('%s loaded.', this.src); 520 | } 521 | loadCount++; 522 | if (loadCount >= images.length) { 523 | patternGround = context.createPattern(images[1], "repeat"); 524 | setStatus('ready'); 525 | } 526 | }; 527 | result.src = imageHost + url; 528 | return result; 529 | }); 530 | }, 531 | initEvent: function() { 532 | var self = this; 533 | var content = canvas.parentNode; 534 | function resize() { 535 | canvas.style.height = window.innerHeight + 'px'; 536 | canvas.style.width = window.innerHeight * (canvas.width / canvas.height) + 'px'; 537 | content.style.width = canvas.style.width; 538 | } 539 | resize(); 540 | window.addEventListener('resize', resize); 541 | window.addEventListener('pageshow', function() { 542 | self.resume(); 543 | }); 544 | 545 | window.addEventListener('pagehide', function() { 546 | self.pause(); 547 | }); 548 | window.addEventListener("keydown", function(event) { 549 | KeyState[event.keyCode] = true; 550 | }, true); 551 | 552 | window.addEventListener("keyup", function(event) { 553 | KeyState[event.keyCode] = false; 554 | }, true); 555 | function mousedown(e) { 556 | switch (status) { 557 | case 'playing': 558 | fly(); 559 | break; 560 | case 'gameover': 561 | var pos = [ 562 | typeof e.offsetX == 'number' ? e.offsetX : e.layerX, 563 | typeof e.offsetY == 'number' ? e.offsetY : e.layerY 564 | ]; 565 | pos[0] *= canvas.width / canvas.offsetWidth; 566 | pos[1] *= canvas.height / canvas.offsetHeight; 567 | if (ptInRect(pos, startInfo)) { 568 | setStatus('ready'); 569 | } 570 | break; 571 | case 'ready': 572 | self.replay(); 573 | break; 574 | } 575 | } 576 | canvas.addEventListener('touchdown', mousedown); 577 | canvas.addEventListener('mousedown', mousedown); 578 | }, 579 | replay: function() { 580 | statusTick = 0; 581 | setStatus('playing'); 582 | }, 583 | onStart: function() { 584 | }, 585 | onPause: function() { 586 | if (status == 'playing') { 587 | setStatus('pause'); 588 | } 589 | }, 590 | onResume: function() { 591 | if (status == 'pause') { 592 | setStatus('playing'); 593 | } 594 | }, 595 | handleInput: function(timeStep, now) { 596 | if (KeyState[Key.Enter]) { 597 | if (status == 'gameover') { 598 | setStatus('ready'); 599 | } 600 | } 601 | if (KeyState[Key.Space] || KeyState[Key.Up]) { 602 | if (status == 'ready') { 603 | this.replay(); 604 | } 605 | if (status == 'playing') { 606 | fly(); 607 | KeyState[Key.Space] = 0; // 不是飞机 608 | KeyState[Key.Up] = 0; 609 | } 610 | } 611 | if (KeyState[Key.P]) { 612 | this.pause(); 613 | } 614 | if (KeyState[Key.R]) { 615 | this.resume(); 616 | } 617 | }, 618 | update: function(timeStep, now) { 619 | statusTick += timeStep; 620 | switch (status) { 621 | case 'playing': 622 | var tick = statusTick; 623 | holdbackOffset = -(tick - holdbackInfo.beginTick) / 1200 * holdbackInfo.distance; 624 | setScore(Math.max(0, (tick - holdbackInfo.beginTick - 100) / 1200)); 625 | tick = new Date - jumpTick; 626 | bridTop = parabola(bridInfo.jumpSpeed, 627 | bridInfo.gravity, tick); 628 | bridTop = Math.min(canvas.height - bridInfo.height - groundInfo.height, 629 | jumpTop + bridTop); 630 | var angle = (bridTop - jumpTop) / 100 + 0.01 * Math.PI; 631 | angle = Math.max(bridInfo.minAngle, 632 | Math.min(bridInfo.maxAngle, angle)); 633 | if (checkOver(bridTop)) { 634 | overAngle = angle; 635 | setStatus('gameover'); 636 | } 637 | break; 638 | } 639 | }, 640 | render: function(timeStep, now) { 641 | if (status == 'loading') { 642 | context.fillStyle = '#000000'; 643 | context.fillRect(0, 0, canvas.width, canvas.height); 644 | } else { 645 | renderBackground(); 646 | renderGround(); 647 | } 648 | var tick = statusTick; 649 | switch (status) { 650 | case 'loading': 651 | renderLoading(); 652 | break; 653 | case 'ready': 654 | renderNumber(score, canvas.width / 2, 150); 655 | renderClock(); 656 | renderReady(0); 657 | renderBrid(bridInfo.top + 15 * Math.cos((tick / 150) % 100)); 658 | break; 659 | case 'playing': 660 | holdbackOffset = -(tick - holdbackInfo.beginTick) / 1200 * holdbackInfo.distance; 661 | renderHoldbacks(); 662 | setScore(Math.max(0, (tick - holdbackInfo.beginTick - 100) / 1200)); 663 | renderNumber(score, canvas.width / 2, 150); 664 | 665 | tick = new Date - jumpTick; 666 | bridTop = parabola(bridInfo.jumpSpeed, 667 | bridInfo.gravity, tick); 668 | bridTop = Math.min(canvas.height - bridInfo.height - groundInfo.height, 669 | jumpTop + bridTop); 670 | var angle = (bridTop - jumpTop) / 100 + 0.01 * Math.PI; 671 | angle = Math.max(bridInfo.minAngle, 672 | Math.min(bridInfo.maxAngle, angle)); 673 | renderBrid(bridTop, angle); 674 | if (checkOver(bridTop)) { 675 | overAngle = angle; 676 | setStatus('gameover'); 677 | } 678 | break; 679 | case 'gameover': 680 | var angle = (bridTop - jumpTop) / 100 + 0.01 * Math.PI; 681 | angle = Math.max(bridInfo.minAngle, 682 | Math.min(bridInfo.maxAngle, angle)); 683 | renderBrid(bridTop, angle, 0); 684 | renderHoldbacks(); 685 | renderBase(0); 686 | renderOver(0); 687 | renderGray(0); 688 | renderYellow(0); 689 | if (bestNew) { 690 | renderNew(0); 691 | } 692 | renderNumber(score, 580, 545, 0.6, 'right'); 693 | renderNumber(best, 580, 650, 0.6, 'right'); 694 | renderStartButton(0); 695 | renderGradeButton(0); 696 | break; 697 | } 698 | context.save(); 699 | context.font = '30px Verdana'; 700 | context.fillStyle = 'red'; 701 | context.fillText('power by The-Best-JS-Game-Framework', 702 | 10, canvas.height - 30); 703 | context.restore(); 704 | } 705 | }); 706 | 707 | }(this); --------------------------------------------------------------------------------