main.js 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. import Player from './player/index'
  2. import Enemy from './npc/enemy'
  3. import BackGround from './runtime/background'
  4. import GameInfo from './runtime/gameinfo'
  5. import Music from './runtime/music'
  6. import DataBus from './databus'
  7. const ctx = canvas.getContext('2d')
  8. const databus = new DataBus()
  9. /**
  10. * 游戏主函数
  11. */
  12. export default class Main {
  13. constructor() {
  14. // 维护当前requestAnimationFrame的id
  15. this.aniId = 0
  16. this.restart()
  17. }
  18. restart() {
  19. databus.reset()
  20. canvas.removeEventListener(
  21. 'touchstart',
  22. this.touchHandler
  23. )
  24. this.bg = new BackGround(ctx)
  25. this.player = new Player(ctx)
  26. this.gameinfo = new GameInfo()
  27. this.music = new Music()
  28. this.bindLoop = this.loop.bind(this)
  29. this.hasEventBind = false
  30. // 清除上一局的动画
  31. window.cancelAnimationFrame(this.aniId)
  32. this.aniId = window.requestAnimationFrame(
  33. this.bindLoop,
  34. canvas
  35. )
  36. }
  37. /**
  38. * 随着帧数变化的敌机生成逻辑
  39. * 帧数取模定义成生成的频率
  40. */
  41. enemyGenerate() {
  42. if (databus.frame % 30 === 0) {
  43. const enemy = databus.pool.getItemByClass('enemy', Enemy)
  44. enemy.init(6)
  45. databus.enemys.push(enemy)
  46. }
  47. }
  48. // 全局碰撞检测
  49. collisionDetection() {
  50. const that = this
  51. databus.bullets.forEach((bullet) => {
  52. for (let i = 0, il = databus.enemys.length; i < il; i++) {
  53. const enemy = databus.enemys[i]
  54. if (!enemy.isPlaying && enemy.isCollideWith(bullet)) {
  55. enemy.playAnimation()
  56. that.music.playExplosion()
  57. bullet.visible = false
  58. databus.score += 1
  59. break
  60. }
  61. }
  62. })
  63. for (let i = 0, il = databus.enemys.length; i < il; i++) {
  64. const enemy = databus.enemys[i]
  65. if (this.player.isCollideWith(enemy)) {
  66. databus.gameOver = true
  67. break
  68. }
  69. }
  70. }
  71. // 游戏结束后的触摸事件处理逻辑
  72. touchEventHandler(e) {
  73. e.preventDefault()
  74. const x = e.touches[0].clientX
  75. const y = e.touches[0].clientY
  76. const area = this.gameinfo.btnArea
  77. if (x >= area.startX
  78. && x <= area.endX
  79. && y >= area.startY
  80. && y <= area.endY) this.restart()
  81. }
  82. /**
  83. * canvas重绘函数
  84. * 每一帧重新绘制所有的需要展示的元素
  85. */
  86. render() {
  87. ctx.clearRect(0, 0, canvas.width, canvas.height)
  88. this.bg.render(ctx)
  89. databus.bullets
  90. .concat(databus.enemys)
  91. .forEach((item) => {
  92. item.drawToCanvas(ctx)
  93. })
  94. this.player.drawToCanvas(ctx)
  95. databus.animations.forEach((ani) => {
  96. if (ani.isPlaying) {
  97. ani.aniRender(ctx)
  98. }
  99. })
  100. this.gameinfo.renderGameScore(ctx, databus.score)
  101. // 游戏结束停止帧循环
  102. if (databus.gameOver) {
  103. this.gameinfo.renderGameOver(ctx, databus.score)
  104. if (!this.hasEventBind) {
  105. this.hasEventBind = true
  106. this.touchHandler = this.touchEventHandler.bind(this)
  107. canvas.addEventListener('touchstart', this.touchHandler)
  108. }
  109. }
  110. }
  111. // 游戏逻辑更新主函数
  112. update() {
  113. if (databus.gameOver) return
  114. this.bg.update()
  115. databus.bullets
  116. .concat(databus.enemys)
  117. .forEach((item) => {
  118. item.update()
  119. })
  120. this.enemyGenerate()
  121. this.collisionDetection()
  122. if (databus.frame % 20 === 0) {
  123. this.player.shoot()
  124. this.music.playShoot()
  125. }
  126. }
  127. // 实现游戏帧循环
  128. loop() {
  129. databus.frame++
  130. this.update()
  131. this.render()
  132. this.aniId = window.requestAnimationFrame(
  133. this.bindLoop,
  134. canvas
  135. )
  136. }
  137. }