index.html 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <script src="https://cdn.jsdelivr.net/npm/phaser/dist/phaser.js"></script>
  5. </head>
  6. <body>
  7. <style>
  8. body {
  9. overflow: hidden;
  10. padding: 0px;
  11. margin: 0px;
  12. }
  13. </style>
  14. <script>
  15. const _GRAVITY = 800;
  16. const _MAX_UPWARDS_VELOCITY = -300;
  17. const _UPWARDS_ACCELERATION = -450;
  18. const _TERMINAL_VELOCITY = 400;
  19. const _PIPE_SPACING_Y = 100;
  20. const _TREADMILL_SPEED = -125;
  21. const _CONFIG_WIDTH = 960;
  22. const _CONFIG_HEIGHT = 540;
  23. const _GROUND_Y = _CONFIG_HEIGHT;
  24. class FlappyBirdObject {
  25. constructor(scene) {
  26. this._sprite = scene.add.sprite(50, 100, 'bird');
  27. this._velocity = 0;
  28. }
  29. Destroy() {
  30. this._sprite.destroy();
  31. }
  32. Update(timeElapsed, keyboard) {
  33. // Apply gravity and upward impulses
  34. this._ApplyGravity(timeElapsed)
  35. this._HandleInput(timeElapsed, keyboard)
  36. this._velocity = Math.min(Math.max(this._velocity, _MAX_UPWARDS_VELOCITY), _TERMINAL_VELOCITY);
  37. this._sprite.y += this._velocity * timeElapsed;
  38. const v = new Phaser.Math.Vector2(-1 * _TREADMILL_SPEED * timeElapsed, 0);
  39. v.add(new Phaser.Math.Vector2(0, this._velocity));
  40. v.normalize();
  41. const rad = Math.atan2(v.y, v.x);
  42. const deg = (180.0 / Math.PI) * rad;
  43. this._sprite.angle = deg * 0.75;
  44. }
  45. get Bounds() {
  46. return this._sprite.getBounds();
  47. }
  48. _ApplyGravity(timeElapsed) {
  49. this._velocity += _GRAVITY * timeElapsed;
  50. }
  51. _HandleInput(timeElapsed, keys) {
  52. if (!Phaser.Input.Keyboard.JustDown(keys.up)) {
  53. return;
  54. }
  55. this._velocity += _UPWARDS_ACCELERATION;
  56. }
  57. }
  58. class PipePairObject {
  59. constructor(scene, x) {
  60. const height = _CONFIG_HEIGHT * (0.25 + 0.5 * Math.random());
  61. this._sprite1 = scene.add.sprite(x, height + _PIPE_SPACING_Y * 0.5, 'pipe');
  62. this._sprite1.displayOriginX = 0;
  63. this._sprite1.displayOriginY = 0;
  64. this._sprite2 = scene.add.sprite(x, height - _PIPE_SPACING_Y * 0.5, 'pipe');
  65. this._sprite2.displayOriginX = 0;
  66. this._sprite2.displayOriginY = 0;
  67. this._sprite2.displayHeight = -1 * this._sprite2.height;
  68. }
  69. Destroy() {
  70. this._sprite1.destroy();
  71. this._sprite2.destroy();
  72. }
  73. Update(timeElapsed) {
  74. this._sprite1.x += timeElapsed * _TREADMILL_SPEED;
  75. this._sprite2.x += timeElapsed * _TREADMILL_SPEED;
  76. }
  77. Intersects(aabb) {
  78. const b1 = this._sprite1.getBounds();
  79. const b2 = this._sprite2.getBounds();
  80. b2.y -= this._sprite2.height;
  81. return (
  82. Phaser.Geom.Intersects.RectangleToRectangle(b1, aabb) ||
  83. Phaser.Geom.Intersects.RectangleToRectangle(b2, aabb));
  84. }
  85. Reset(x) {
  86. const height = _CONFIG_HEIGHT * (0.25 + 0.5 * Math.random());
  87. this._sprite1.x = x;
  88. this._sprite1.y = height + _PIPE_SPACING_Y * 0.5;
  89. this._sprite2.x = x;
  90. this._sprite2.y = height - _PIPE_SPACING_Y * 0.5;
  91. }
  92. get X() {
  93. return this._sprite1.x;
  94. }
  95. get Width() {
  96. return this._sprite1.width;
  97. }
  98. }
  99. class FlappyBirdGame {
  100. constructor() {
  101. this._game = this._CreateGame();
  102. this._previousFrame = null;
  103. this._bird = null;
  104. this._gameOver = false;
  105. this._score = 0;
  106. this._scoreText = null;
  107. this._gameOverText = null;
  108. this._pipes = [];
  109. }
  110. _Destroy() {
  111. this._bird.Destroy();
  112. for (let p of this._pipes) {
  113. p.Destroy();
  114. }
  115. this._scoreText.destroy();
  116. if (this._gameOverText !== null) {
  117. this._gameOverText.destroy();
  118. }
  119. this._bird = null;
  120. this._pipes = [];
  121. this._previousFrame = null;
  122. }
  123. _Init() {
  124. for (let i = 0; i < 5; i+=1) {
  125. this._pipes.push(new PipePairObject(this._scene, 500 + i * 250));
  126. }
  127. this._bird = new FlappyBirdObject(this._scene);
  128. this._gameOver = false;
  129. this._score = 0;
  130. }
  131. _CreateGame() {
  132. const self = this;
  133. const config = {
  134. type: Phaser.AUTO,
  135. scene: {
  136. preload: function() { self._OnPreload(this); },
  137. create: function() { self._OnCreate(this); },
  138. update: function() { self._OnUpdate(this); },
  139. },
  140. scale: {
  141. mode: Phaser.Scale.FIT,
  142. autoCenter: Phaser.Scale.CENTER_BOTH,
  143. width: _CONFIG_WIDTH,
  144. height: _CONFIG_HEIGHT
  145. }
  146. };
  147. return new Phaser.Game(config);
  148. }
  149. _OnPreload(scene) {
  150. this._scene = scene;
  151. this._scene.load.image('sky', 'assets/sky.png');
  152. this._scene.load.image('bird', 'assets/bird.png');
  153. this._scene.load.image('pipe', 'assets/pipe.png');
  154. }
  155. _OnCreate(scene) {
  156. const s = this._scene.add.image(0, 0, 'sky');
  157. s.displayOriginX = 0;
  158. s.displayOriginY = 0;
  159. s.displayWidth = _CONFIG_WIDTH;
  160. s.displayHeight = _CONFIG_HEIGHT;
  161. this._keys = {
  162. up: this._scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.UP),
  163. f: this._scene.input.keyboard.addKey('F'),
  164. r: this._scene.input.keyboard.addKey('R'),
  165. }
  166. this._keys.f.on('down', function () {
  167. if (this._scene.scale.isFullscreen) {
  168. this._scene.scale.stopFullscreen();
  169. } else {
  170. this._scene.scale.startFullscreen();
  171. }
  172. }, this);
  173. this._keys.r.on('down', function () {
  174. this._Destroy();
  175. this._Init();
  176. this._DrawScore();
  177. }, this);
  178. this._Init();
  179. this._DrawScore();
  180. }
  181. _OnUpdate(scene) {
  182. if (this._gameOver) {
  183. return;
  184. }
  185. const currentFrame = scene.time.now;
  186. if (this._previousFrame == null) {
  187. this._previousFrame = currentFrame;
  188. }
  189. const timeElapsedInS = (currentFrame - this._previousFrame) / 1000.0;
  190. this._bird.Update(timeElapsedInS, this._keys);
  191. this._UpdatePipes(timeElapsedInS);
  192. this._CheckGameOver();
  193. this._previousFrame = currentFrame;
  194. }
  195. _CheckGameOver() {
  196. // Ground check
  197. const birdAABB = this._bird.Bounds;
  198. birdAABB.top += 10;
  199. birdAABB.bottom -= 10;
  200. birdAABB.left += 10;
  201. birdAABB.right -= 10;
  202. if (birdAABB.top >= _GROUND_Y) {
  203. this._GameOver();
  204. return;
  205. }
  206. for (const p of this._pipes) {
  207. if (p.Intersects(birdAABB)) {
  208. let a = p.Intersects(birdAABB);
  209. this._GameOver();
  210. return;
  211. }
  212. }
  213. }
  214. _UpdatePipes(timeElapsed) {
  215. const oldPipeX = this._pipes[0].X + this._pipes[0].Width;
  216. for (const p of this._pipes) {
  217. p.Update(timeElapsed);
  218. }
  219. const newPipeX = this._pipes[0].X + this._pipes[0].Width;
  220. if (oldPipeX > 50 && newPipeX <= 50) {
  221. this._score += 1;
  222. this._scoreText.text = "Score: " + this._score;
  223. }
  224. if ((this._pipes[0].X + this._pipes[0].Width) <= 0) {
  225. const p = this._pipes.shift();
  226. p.Reset(this._pipes[this._pipes.length - 1].X + 200.0);
  227. this._pipes.push(p);
  228. }
  229. }
  230. _GameOver() {
  231. const text = "GAME OVER";
  232. const style = {
  233. font: "100px Roboto",
  234. fill: "#FFFFFF",
  235. align: "center",
  236. fixedWidth: _CONFIG_WIDTH,
  237. shadow: {
  238. offsetX: 2,
  239. offsetY: 2,
  240. color: "#000",
  241. blur: 2,
  242. fill: true
  243. }
  244. };
  245. this._gameOverText = this._scene.add.text(0, _CONFIG_HEIGHT * 0.25, text, style);
  246. this._gameOver = true;
  247. }
  248. _DrawScore() {
  249. const text = "Score: 0";
  250. const style = {
  251. font: "40px Roboto",
  252. fill: "#FFFFFF",
  253. align: "center",
  254. shadow: {
  255. offsetX: 2,
  256. offsetY: 2,
  257. color: "#000",
  258. blur: 2,
  259. fill: true
  260. }
  261. };
  262. this._scoreText = this._scene.add.text(0, 0, text, style);
  263. }
  264. }
  265. _GAME = new FlappyBirdGame()
  266. </script>
  267. </body>
  268. </html>