优化地图交互与文档方案

This commit is contained in:
2026-03-26 12:20:27 +08:00
parent ce25530938
commit d695308a55
9 changed files with 2196 additions and 69 deletions

View File

@@ -35,6 +35,7 @@ type MapPageData = MapEngineViewState & {
showDebugPanel: boolean
showGameInfoPanel: boolean
showCenterScaleRuler: boolean
showPunchHintBanner: boolean
centerScaleRulerAnchorMode: CenterScaleRulerAnchorMode
statusBarHeight: number
topInsetHeight: number
@@ -74,11 +75,150 @@ type MapPageData = MapEngineViewState & {
showRightButtonGroups: boolean
showBottomDebugButton: boolean
}
const INTERNAL_BUILD_VERSION = 'map-build-252'
const INTERNAL_BUILD_VERSION = 'map-build-261'
const CLASSIC_REMOTE_GAME_CONFIG_URL = 'https://oss-mbh5.colormaprun.com/gotomars/event/classic-sequential.json'
const SCORE_O_REMOTE_GAME_CONFIG_URL = 'https://oss-mbh5.colormaprun.com/gotomars/event/score-o.json'
const PUNCH_HINT_AUTO_HIDE_MS = 30000
let mapEngine: MapEngine | null = null
let stageCanvasAttached = false
let gameInfoPanelSyncTimer = 0
let centerScaleRulerSyncTimer = 0
let punchHintDismissTimer = 0
const DEBUG_ONLY_VIEW_KEYS = new Set<string>([
'buildVersion',
'renderMode',
'projectionMode',
'mapReady',
'mapReadyText',
'mapName',
'configStatusText',
'sensorHeadingText',
'deviceHeadingText',
'devicePoseText',
'headingConfidenceText',
'accelerometerText',
'gyroscopeText',
'deviceMotionText',
'compassDeclinationText',
'northReferenceButtonText',
'autoRotateSourceText',
'autoRotateCalibrationText',
'northReferenceText',
'centerText',
'tileSource',
'visibleTileCount',
'readyTileCount',
'memoryTileCount',
'diskTileCount',
'memoryHitCount',
'diskHitCount',
'networkFetchCount',
'cacheHitRateText',
'locationSourceMode',
'locationSourceText',
'mockBridgeConnected',
'mockBridgeStatusText',
'mockBridgeUrlText',
'mockCoordText',
'mockSpeedText',
'gpsCoordText',
'heartRateSourceMode',
'heartRateSourceText',
'heartRateConnected',
'heartRateStatusText',
'heartRateDeviceText',
'heartRateScanText',
'heartRateDiscoveredDevices',
'mockHeartRateBridgeConnected',
'mockHeartRateBridgeStatusText',
'mockHeartRateBridgeUrlText',
'mockHeartRateText',
])
const CENTER_SCALE_RULER_DEP_KEYS = new Set<string>([
'showCenterScaleRuler',
'centerScaleRulerAnchorMode',
'stageWidth',
'stageHeight',
'topInsetHeight',
'zoom',
'centerTileY',
'tileSizePx',
'previewScale',
])
const RULER_ONLY_VIEW_KEYS = new Set<string>([
'zoom',
'centerTileX',
'centerTileY',
'tileSizePx',
'previewScale',
'stageWidth',
'stageHeight',
'stageLeft',
'stageTop',
])
const SIDE_BUTTON_DEP_KEYS = new Set<string>([
'sideButtonMode',
'showGameInfoPanel',
'showCenterScaleRuler',
'centerScaleRulerAnchorMode',
'skipButtonEnabled',
'gameSessionStatus',
'gpsLockEnabled',
'gpsLockAvailable',
])
function hasAnyPatchKey(patch: Record<string, unknown>, keys: Set<string>): boolean {
return Object.keys(patch).some((key) => keys.has(key))
}
function filterDebugOnlyPatch(
patch: Partial<MapPageData>,
includeDebugFields: boolean,
includeRulerFields: boolean,
): Partial<MapPageData> {
if (includeDebugFields && includeRulerFields) {
return patch
}
const filteredPatch: Partial<MapPageData> = {}
for (const [key, value] of Object.entries(patch)) {
if (!includeDebugFields && DEBUG_ONLY_VIEW_KEYS.has(key)) {
continue
}
if (!includeRulerFields && RULER_ONLY_VIEW_KEYS.has(key)) {
continue
}
{
;(filteredPatch as Record<string, unknown>)[key] = value
}
}
return filteredPatch
}
function clearGameInfoPanelSyncTimer() {
if (gameInfoPanelSyncTimer) {
clearTimeout(gameInfoPanelSyncTimer)
gameInfoPanelSyncTimer = 0
}
}
function clearCenterScaleRulerSyncTimer() {
if (centerScaleRulerSyncTimer) {
clearTimeout(centerScaleRulerSyncTimer)
centerScaleRulerSyncTimer = 0
}
}
function clearPunchHintDismissTimer() {
if (punchHintDismissTimer) {
clearTimeout(punchHintDismissTimer)
punchHintDismissTimer = 0
}
}
function buildSideButtonVisibility(mode: SideButtonMode) {
return {
sideButtonMode: mode,
@@ -389,6 +529,7 @@ Page({
panelDistanceValueText: '--',
panelDistanceUnitText: '',
panelProgressText: '0/0',
showPunchHintBanner: true,
gameSessionStatus: 'idle',
gameModeText: '顺序赛',
gpsLockEnabled: false,
@@ -488,9 +629,11 @@ Page({
mapEngine = new MapEngine(INTERNAL_BUILD_VERSION, {
onData: (patch) => {
const nextPatch = patch as Partial<MapPageData>
const nextData: Partial<MapPageData> = {
const includeDebugFields = this.data.showDebugPanel
const includeRulerFields = this.data.showCenterScaleRuler
const nextData: Partial<MapPageData> = filterDebugOnlyPatch({
...nextPatch,
}
}, includeDebugFields, includeRulerFields)
if (
typeof nextPatch.mockBridgeUrlText === 'string'
@@ -511,18 +654,52 @@ Page({
...nextData,
} as MapPageData
this.setData({
...nextData,
...buildCenterScaleRulerPatch(mergedData),
...buildSideButtonState(mergedData),
})
const derivedPatch: Partial<MapPageData> = {}
if (
this.data.showCenterScaleRuler
&& hasAnyPatchKey(nextPatch as Record<string, unknown>, CENTER_SCALE_RULER_DEP_KEYS)
) {
Object.assign(derivedPatch, buildCenterScaleRulerPatch(mergedData))
}
if (hasAnyPatchKey(nextPatch as Record<string, unknown>, SIDE_BUTTON_DEP_KEYS)) {
Object.assign(derivedPatch, buildSideButtonState(mergedData))
}
if (typeof nextPatch.punchHintText === 'string') {
const nextHintText = nextPatch.punchHintText.trim()
if (nextHintText !== this.data.punchHintText) {
clearPunchHintDismissTimer()
nextData.showPunchHintBanner = nextHintText.length > 0
if (nextHintText.length > 0) {
punchHintDismissTimer = setTimeout(() => {
punchHintDismissTimer = 0
this.setData({
showPunchHintBanner: false,
})
}, PUNCH_HINT_AUTO_HIDE_MS) as unknown as number
}
} else if (!nextHintText) {
clearPunchHintDismissTimer()
nextData.showPunchHintBanner = false
}
}
if (Object.keys(nextData).length || Object.keys(derivedPatch).length) {
this.setData({
...nextData,
...derivedPatch,
})
}
if (this.data.showGameInfoPanel) {
this.syncGameInfoPanelSnapshot()
this.scheduleGameInfoPanelSnapshotSync()
}
},
})
mapEngine.setDiagnosticUiEnabled(false)
this.setData({
...mapEngine.getInitialData(),
showDebugPanel: false,
@@ -542,6 +719,7 @@ Page({
panelDistanceValueText: '--',
panelDistanceUnitText: '',
panelProgressText: '0/0',
showPunchHintBanner: true,
gameSessionStatus: 'idle',
gameModeText: '顺序赛',
gpsLockEnabled: false,
@@ -647,6 +825,9 @@ Page({
},
onUnload() {
clearGameInfoPanelSyncTimer()
clearCenterScaleRulerSyncTimer()
clearPunchHintDismissTimer()
if (mapEngine) {
mapEngine.destroy()
mapEngine = null
@@ -686,7 +867,7 @@ Page({
})
},
measureStageAndCanvas() {
measureStageAndCanvas(onApplied?: () => void) {
const page = this
const applyStage = (rawRect?: Partial<WechatMiniprogram.BoundingClientRectCallbackResult>) => {
const fallbackRect = getFallbackStageRect()
@@ -703,6 +884,9 @@ Page({
}
currentEngine.setStage(rect)
if (onApplied) {
onApplied()
}
if (stageCanvasAttached) {
return
@@ -1053,7 +1237,26 @@ Page({
})
},
scheduleGameInfoPanelSnapshotSync() {
if (!this.data.showGameInfoPanel) {
clearGameInfoPanelSyncTimer()
return
}
if (gameInfoPanelSyncTimer) {
return
}
gameInfoPanelSyncTimer = setTimeout(() => {
gameInfoPanelSyncTimer = 0
if (this.data.showGameInfoPanel) {
this.syncGameInfoPanelSnapshot()
}
}, 400) as unknown as number
},
handleOpenGameInfoPanel() {
clearGameInfoPanelSyncTimer()
this.syncGameInfoPanelSnapshot()
this.setData({
showDebugPanel: false,
@@ -1072,6 +1275,7 @@ Page({
},
handleCloseGameInfoPanel() {
clearGameInfoPanelSyncTimer()
this.setData({
showGameInfoPanel: false,
...buildSideButtonState({
@@ -1107,6 +1311,13 @@ Page({
}
},
handleClosePunchHint() {
clearPunchHintDismissTimer()
this.setData({
showPunchHintBanner: false,
})
},
handleHudPanelChange(event: WechatMiniprogram.CustomEvent<{ current: number }>) {
this.setData({
hudPanelIndex: event.detail.current || 0,
@@ -1147,8 +1358,15 @@ Page({
mapEngine.handleSetHeadingUpMode()
},
handleToggleDebugPanel() {
const nextShowDebugPanel = !this.data.showDebugPanel
if (!nextShowDebugPanel) {
clearGameInfoPanelSyncTimer()
}
if (mapEngine) {
mapEngine.setDiagnosticUiEnabled(nextShowDebugPanel)
}
this.setData({
showDebugPanel: !this.data.showDebugPanel,
showDebugPanel: nextShowDebugPanel,
showGameInfoPanel: false,
...buildSideButtonState({
sideButtonMode: this.data.sideButtonMode,
@@ -1164,6 +1382,9 @@ Page({
},
handleCloseDebugPanel() {
if (mapEngine) {
mapEngine.setDiagnosticUiEnabled(false)
}
this.setData({
showDebugPanel: false,
...buildSideButtonState({
@@ -1182,16 +1403,51 @@ Page({
handleToggleCenterScaleRuler() {
const nextEnabled = !this.data.showCenterScaleRuler
this.data.showCenterScaleRuler = nextEnabled
const mergedData = {
...this.data,
showCenterScaleRuler: nextEnabled,
} as MapPageData
clearCenterScaleRulerSyncTimer()
const syncRulerFromEngine = () => {
if (!mapEngine) {
return
}
const engineSnapshot = mapEngine.getInitialData() as Partial<MapPageData>
const mergedData = {
...engineSnapshot,
...this.data,
showCenterScaleRuler: nextEnabled,
} as MapPageData
this.setData({
...filterDebugOnlyPatch(engineSnapshot, this.data.showDebugPanel, nextEnabled),
showCenterScaleRuler: nextEnabled,
...buildCenterScaleRulerPatch(mergedData),
...buildSideButtonState(mergedData),
})
}
if (!nextEnabled) {
syncRulerFromEngine()
return
}
this.setData({
showCenterScaleRuler: nextEnabled,
...buildCenterScaleRulerPatch(mergedData),
...buildSideButtonState(mergedData),
showCenterScaleRuler: true,
...buildSideButtonState({
...this.data,
showCenterScaleRuler: true,
} as MapPageData),
})
this.measureStageAndCanvas(() => {
syncRulerFromEngine()
})
centerScaleRulerSyncTimer = setTimeout(() => {
centerScaleRulerSyncTimer = 0
if (!this.data.showCenterScaleRuler) {
return
}
syncRulerFromEngine()
}, 96) as unknown as number
},
handleToggleCenterScaleRulerAnchor() {
@@ -1202,13 +1458,16 @@ Page({
const nextAnchorMode: CenterScaleRulerAnchorMode = this.data.centerScaleRulerAnchorMode === 'screen-center'
? 'compass-center'
: 'screen-center'
const engineSnapshot = mapEngine ? (mapEngine.getInitialData() as Partial<MapPageData>) : {}
this.data.centerScaleRulerAnchorMode = nextAnchorMode
const mergedData = {
...engineSnapshot,
...this.data,
centerScaleRulerAnchorMode: nextAnchorMode,
} as MapPageData
this.setData({
...filterDebugOnlyPatch(engineSnapshot, this.data.showDebugPanel, true),
centerScaleRulerAnchorMode: nextAnchorMode,
...buildCenterScaleRulerPatch(mergedData),
...buildSideButtonState(mergedData),