map.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. import { MapEngine, type MapEngineStageRect, type MapEngineViewState } from '../../engine/map/mapEngine'
  2. import { loadRemoteMapConfig } from '../../utils/remoteMapConfig'
  3. type CompassTickData = {
  4. angle: number
  5. long: boolean
  6. major: boolean
  7. }
  8. type CompassLabelData = {
  9. text: string
  10. angle: number
  11. rotateBack: number
  12. radius: number
  13. className: string
  14. }
  15. type SideButtonMode = 'all' | 'left' | 'right' | 'hidden'
  16. type MapPageData = MapEngineViewState & {
  17. showDebugPanel: boolean
  18. statusBarHeight: number
  19. topInsetHeight: number
  20. panelTimerText: string
  21. panelMileageText: string
  22. panelDistanceValueText: string
  23. panelProgressText: string
  24. panelSpeedValueText: string
  25. compassTicks: CompassTickData[]
  26. compassLabels: CompassLabelData[]
  27. sideButtonMode: SideButtonMode
  28. showLeftButtonGroup: boolean
  29. showRightButtonGroups: boolean
  30. showBottomDebugButton: boolean
  31. }
  32. const INTERNAL_BUILD_VERSION = 'map-build-134'
  33. const REMOTE_GAME_CONFIG_URL = 'https://oss-mbh5.colormaprun.com/wxmini/test/game.json'
  34. let mapEngine: MapEngine | null = null
  35. function buildSideButtonVisibility(mode: SideButtonMode) {
  36. return {
  37. sideButtonMode: mode,
  38. showLeftButtonGroup: mode === 'all' || mode === 'left' || mode === 'right',
  39. showRightButtonGroups: mode === 'all' || mode === 'right',
  40. showBottomDebugButton: mode !== 'hidden',
  41. }
  42. }
  43. function getNextSideButtonMode(currentMode: SideButtonMode): SideButtonMode {
  44. if (currentMode === 'all') {
  45. return 'left'
  46. }
  47. if (currentMode === 'left') {
  48. return 'right'
  49. }
  50. if (currentMode === 'right') {
  51. return 'hidden'
  52. }
  53. return 'left'
  54. }
  55. function buildCompassTicks(): CompassTickData[] {
  56. const ticks: CompassTickData[] = []
  57. for (let angle = 0; angle < 360; angle += 5) {
  58. ticks.push({
  59. angle,
  60. long: angle % 15 === 0,
  61. major: angle % 45 === 0,
  62. })
  63. }
  64. return ticks
  65. }
  66. function buildCompassLabels(): CompassLabelData[] {
  67. return [
  68. { text: '\u5317', angle: 0, rotateBack: 0, radius: 68, className: 'compass-widget__mark--cardinal compass-widget__mark--north' },
  69. { text: '\u4e1c\u5317', angle: 45, rotateBack: 0, radius: 58, className: 'compass-widget__mark--intermediate compass-widget__mark--northeast' },
  70. { text: '\u4e1c', angle: 90, rotateBack: 0, radius: 68, className: 'compass-widget__mark--cardinal' },
  71. { text: '\u4e1c\u5357', angle: 135, rotateBack: 0, radius: 58, className: 'compass-widget__mark--intermediate' },
  72. { text: '\u5357', angle: 180, rotateBack: 0, radius: 68, className: 'compass-widget__mark--cardinal' },
  73. { text: '\u897f\u5357', angle: 225, rotateBack: 0, radius: 58, className: 'compass-widget__mark--intermediate' },
  74. { text: '\u897f', angle: 270, rotateBack: 0, radius: 68, className: 'compass-widget__mark--cardinal' },
  75. { text: '\u897f\u5317', angle: 315, rotateBack: 0, radius: 58, className: 'compass-widget__mark--intermediate compass-widget__mark--northwest' },
  76. ]
  77. }
  78. function getFallbackStageRect(): MapEngineStageRect {
  79. const systemInfo = wx.getSystemInfoSync()
  80. const width = Math.max(320, systemInfo.windowWidth)
  81. const height = Math.max(280, systemInfo.windowHeight)
  82. return {
  83. width,
  84. height,
  85. left: 0,
  86. top: 0,
  87. }
  88. }
  89. Page({
  90. data: {
  91. showDebugPanel: false,
  92. statusBarHeight: 0,
  93. topInsetHeight: 12,
  94. panelTimerText: '00:00:00',
  95. panelMileageText: '0m',
  96. panelDistanceValueText: '108',
  97. panelProgressText: '0/0',
  98. gameSessionStatus: 'idle',
  99. panelSpeedValueText: '0',
  100. punchButtonText: '打点',
  101. punchButtonEnabled: false,
  102. punchHintText: '等待进入检查点范围',
  103. punchFeedbackVisible: false,
  104. punchFeedbackText: '',
  105. punchFeedbackTone: 'neutral',
  106. contentCardVisible: false,
  107. contentCardTitle: '',
  108. contentCardBody: '',
  109. punchButtonFxClass: '',
  110. punchFeedbackFxClass: '',
  111. contentCardFxClass: '',
  112. mapPulseVisible: false,
  113. mapPulseLeftPx: 0,
  114. mapPulseTopPx: 0,
  115. mapPulseFxClass: '',
  116. stageFxVisible: false,
  117. stageFxClass: '',
  118. compassTicks: buildCompassTicks(),
  119. compassLabels: buildCompassLabels(),
  120. ...buildSideButtonVisibility('left'),
  121. } as MapPageData,
  122. onLoad() {
  123. const systemInfo = wx.getSystemInfoSync()
  124. const statusBarHeight = systemInfo.statusBarHeight || 0
  125. const menuButtonRect = wx.getMenuButtonBoundingClientRect()
  126. const menuButtonBottom = menuButtonRect && typeof menuButtonRect.bottom === 'number' ? menuButtonRect.bottom : statusBarHeight
  127. mapEngine = new MapEngine(INTERNAL_BUILD_VERSION, {
  128. onData: (patch) => {
  129. this.setData(patch)
  130. },
  131. })
  132. this.setData({
  133. ...mapEngine.getInitialData(),
  134. showDebugPanel: false,
  135. statusBarHeight,
  136. topInsetHeight: Math.max(statusBarHeight + 12, menuButtonBottom + 20),
  137. panelTimerText: '00:00:00',
  138. panelMileageText: '0m',
  139. panelDistanceValueText: '108',
  140. panelProgressText: '0/0',
  141. gameSessionStatus: 'idle',
  142. panelSpeedValueText: '0',
  143. punchButtonText: '打点',
  144. punchButtonEnabled: false,
  145. punchHintText: '等待进入检查点范围',
  146. punchFeedbackVisible: false,
  147. punchFeedbackText: '',
  148. punchFeedbackTone: 'neutral',
  149. contentCardVisible: false,
  150. contentCardTitle: '',
  151. contentCardBody: '',
  152. punchButtonFxClass: '',
  153. punchFeedbackFxClass: '',
  154. contentCardFxClass: '',
  155. mapPulseVisible: false,
  156. mapPulseLeftPx: 0,
  157. mapPulseTopPx: 0,
  158. mapPulseFxClass: '',
  159. stageFxVisible: false,
  160. stageFxClass: '',
  161. compassTicks: buildCompassTicks(),
  162. compassLabels: buildCompassLabels(),
  163. ...buildSideButtonVisibility('left'),
  164. })
  165. },
  166. onReady() {
  167. this.measureStageAndCanvas()
  168. this.loadMapConfigFromRemote()
  169. },
  170. onUnload() {
  171. if (mapEngine) {
  172. mapEngine.destroy()
  173. mapEngine = null
  174. }
  175. },
  176. loadMapConfigFromRemote() {
  177. const currentEngine = mapEngine
  178. if (!currentEngine) {
  179. return
  180. }
  181. loadRemoteMapConfig(REMOTE_GAME_CONFIG_URL)
  182. .then((config) => {
  183. if (mapEngine !== currentEngine) {
  184. return
  185. }
  186. currentEngine.applyRemoteMapConfig(config)
  187. })
  188. .catch((error) => {
  189. if (mapEngine !== currentEngine) {
  190. return
  191. }
  192. const errorMessage = error && error.message ? error.message : '鏈煡閿欒'
  193. this.setData({
  194. configStatusText: `杞藉叆澶辫触: ${errorMessage}`,
  195. statusText: `杩滅▼鍦板浘閰嶇疆杞藉叆澶辫触: ${errorMessage} (${INTERNAL_BUILD_VERSION})`,
  196. })
  197. })
  198. },
  199. measureStageAndCanvas() {
  200. const page = this
  201. const applyStage = (rawRect?: Partial<WechatMiniprogram.BoundingClientRectCallbackResult>) => {
  202. const fallbackRect = getFallbackStageRect()
  203. const rect: MapEngineStageRect = {
  204. width: rawRect && typeof rawRect.width === 'number' ? rawRect.width : fallbackRect.width,
  205. height: rawRect && typeof rawRect.height === 'number' ? rawRect.height : fallbackRect.height,
  206. left: rawRect && typeof rawRect.left === 'number' ? rawRect.left : fallbackRect.left,
  207. top: rawRect && typeof rawRect.top === 'number' ? rawRect.top : fallbackRect.top,
  208. }
  209. const currentEngine = mapEngine
  210. if (!currentEngine) {
  211. return
  212. }
  213. currentEngine.setStage(rect)
  214. const canvasQuery = wx.createSelectorQuery().in(page)
  215. canvasQuery.select('#mapCanvas').fields({ node: true, size: true })
  216. canvasQuery.select('#routeLabelCanvas').fields({ node: true, size: true })
  217. canvasQuery.exec((canvasRes) => {
  218. const canvasRef = canvasRes[0] as any
  219. const labelCanvasRef = canvasRes[1] as any
  220. if (!canvasRef || !canvasRef.node) {
  221. page.setData({
  222. statusText: `WebGL 寮曟搸鍒濆鍖栧け璐?(${INTERNAL_BUILD_VERSION})`,
  223. })
  224. return
  225. }
  226. const dpr = wx.getSystemInfoSync().pixelRatio || 1
  227. try {
  228. currentEngine.attachCanvas(
  229. canvasRef.node,
  230. rect.width,
  231. rect.height,
  232. dpr,
  233. labelCanvasRef && labelCanvasRef.node ? labelCanvasRef.node : undefined,
  234. )
  235. } catch (error) {
  236. page.setData({
  237. statusText: `WebGL 鍒濆鍖栧け璐?(${INTERNAL_BUILD_VERSION})`,
  238. })
  239. }
  240. })
  241. }
  242. const query = wx.createSelectorQuery().in(page)
  243. query.select('.map-stage').boundingClientRect()
  244. query.exec((res) => {
  245. const rect = res[0] as WechatMiniprogram.BoundingClientRectCallbackResult | undefined
  246. applyStage(rect)
  247. })
  248. },
  249. handleTouchStart(event: WechatMiniprogram.TouchEvent) {
  250. if (mapEngine) {
  251. mapEngine.handleTouchStart(event)
  252. }
  253. },
  254. handleTouchMove(event: WechatMiniprogram.TouchEvent) {
  255. if (mapEngine) {
  256. mapEngine.handleTouchMove(event)
  257. }
  258. },
  259. handleTouchEnd(event: WechatMiniprogram.TouchEvent) {
  260. if (mapEngine) {
  261. mapEngine.handleTouchEnd(event)
  262. }
  263. },
  264. handleTouchCancel() {
  265. if (mapEngine) {
  266. mapEngine.handleTouchCancel()
  267. }
  268. },
  269. handleRecenter() {
  270. if (mapEngine) {
  271. mapEngine.handleRecenter()
  272. }
  273. },
  274. handleRotateStep() {
  275. if (mapEngine) {
  276. mapEngine.handleRotateStep()
  277. }
  278. },
  279. handleRotationReset() {
  280. if (mapEngine) {
  281. mapEngine.handleRotationReset()
  282. }
  283. },
  284. handleSetManualMode() {
  285. if (mapEngine) {
  286. mapEngine.handleSetManualMode()
  287. }
  288. },
  289. handleSetNorthUpMode() {
  290. if (mapEngine) {
  291. mapEngine.handleSetNorthUpMode()
  292. }
  293. },
  294. handleSetHeadingUpMode() {
  295. if (mapEngine) {
  296. mapEngine.handleSetHeadingUpMode()
  297. }
  298. },
  299. handleCycleNorthReferenceMode() {
  300. if (mapEngine) {
  301. mapEngine.handleCycleNorthReferenceMode()
  302. }
  303. },
  304. handleAutoRotateCalibrate() {
  305. if (mapEngine) {
  306. mapEngine.handleAutoRotateCalibrate()
  307. }
  308. },
  309. handleToggleGpsTracking() {
  310. if (mapEngine) {
  311. mapEngine.handleToggleGpsTracking()
  312. }
  313. },
  314. handleToggleOsmReference() {
  315. if (mapEngine) {
  316. mapEngine.handleToggleOsmReference()
  317. }
  318. },
  319. handleStartGame() {
  320. if (mapEngine) {
  321. mapEngine.handleStartGame()
  322. }
  323. },
  324. handleOverlayTouch() {},
  325. handlePunchAction() {
  326. if (!this.data.punchButtonEnabled) {
  327. return
  328. }
  329. if (mapEngine) {
  330. mapEngine.handlePunchAction()
  331. }
  332. },
  333. handleCloseContentCard() {
  334. if (mapEngine) {
  335. mapEngine.closeContentCard()
  336. }
  337. },
  338. handleCycleSideButtons() {
  339. this.setData(buildSideButtonVisibility(getNextSideButtonMode(this.data.sideButtonMode)))
  340. },
  341. handleToggleMapRotateMode() {
  342. if (!mapEngine) {
  343. return
  344. }
  345. if (this.data.orientationMode === 'heading-up') {
  346. mapEngine.handleSetManualMode()
  347. return
  348. }
  349. mapEngine.handleSetHeadingUpMode()
  350. },
  351. handleToggleDebugPanel() {
  352. this.setData({
  353. showDebugPanel: !this.data.showDebugPanel,
  354. })
  355. },
  356. handleCloseDebugPanel() {
  357. this.setData({
  358. showDebugPanel: false,
  359. })
  360. },
  361. handleDebugPanelTap() {},
  362. })