Improve map lock and smart heading behavior

This commit is contained in:
2026-03-25 14:56:28 +08:00
parent d1cc6cc473
commit a19342d61f
8 changed files with 300 additions and 62 deletions

View File

@@ -54,6 +54,10 @@ const AUTO_ROTATE_DEADZONE_DEG = 4
const AUTO_ROTATE_MAX_STEP_DEG = 0.75
const AUTO_ROTATE_HEADING_SMOOTHING = 0.32
const COMPASS_NEEDLE_SMOOTHING = 0.12
const SMART_HEADING_BLEND_START_SPEED_KMH = 1.2
const SMART_HEADING_MOVEMENT_SPEED_KMH = 3.0
const SMART_HEADING_MIN_DISTANCE_METERS = 8
const SMART_HEADING_MAX_ACCURACY_METERS = 25
const GPS_TRACK_MAX_POINTS = 200
const GPS_TRACK_MIN_STEP_METERS = 3
const MAP_TAP_MOVE_THRESHOLD_PX = 14
@@ -64,7 +68,8 @@ type TouchPoint = WechatMiniprogram.TouchDetail
type GestureMode = 'idle' | 'pan' | 'pinch'
type RotationMode = 'manual' | 'auto'
type OrientationMode = 'manual' | 'north-up' | 'heading-up'
type AutoRotateSourceMode = 'sensor' | 'course' | 'fusion'
type AutoRotateSourceMode = 'sensor' | 'course' | 'fusion' | 'smart'
type SmartHeadingSource = 'sensor' | 'blended' | 'movement'
type NorthReferenceMode = 'magnetic' | 'true'
const DEFAULT_NORTH_REFERENCE_MODE: NorthReferenceMode = 'magnetic'
@@ -122,6 +127,8 @@ export interface MapEngineViewState {
statusText: string
gpsTracking: boolean
gpsTrackingText: string
gpsLockEnabled: boolean
gpsLockAvailable: boolean
locationSourceMode: 'real' | 'mock'
locationSourceText: string
mockBridgeConnected: boolean
@@ -244,6 +251,8 @@ const VIEW_SYNC_KEYS: Array<keyof MapEngineViewState> = [
'statusText',
'gpsTracking',
'gpsTrackingText',
'gpsLockEnabled',
'gpsLockAvailable',
'locationSourceMode',
'locationSourceText',
'mockBridgeConnected',
@@ -406,6 +415,10 @@ function formatRotationToggleText(mode: OrientationMode): string {
}
function formatAutoRotateSourceText(mode: AutoRotateSourceMode, hasCourseHeading: boolean): string {
if (mode === 'smart') {
return 'Smart / 手机朝向'
}
if (mode === 'sensor') {
return 'Sensor Only'
}
@@ -417,6 +430,18 @@ function formatAutoRotateSourceText(mode: AutoRotateSourceMode, hasCourseHeading
return hasCourseHeading ? 'Sensor + Course' : 'Sensor Only'
}
function formatSmartHeadingSourceText(source: SmartHeadingSource): string {
if (source === 'movement') {
return 'Smart / 前进方向'
}
if (source === 'blended') {
return 'Smart / 融合'
}
return 'Smart / 手机朝向'
}
function formatAutoRotateCalibrationText(pending: boolean, offsetDeg: number | null): string {
if (pending) {
return 'Pending'
@@ -539,6 +564,18 @@ function getApproxDistanceMeters(a: LonLatPoint, b: LonLatPoint): number {
return Math.sqrt(dx * dx + dy * dy)
}
function resolveSmartHeadingSource(speedKmh: number | null, movementReliable: boolean): SmartHeadingSource {
if (!movementReliable || speedKmh === null || !Number.isFinite(speedKmh) || speedKmh <= SMART_HEADING_BLEND_START_SPEED_KMH) {
return 'sensor'
}
if (speedKmh >= SMART_HEADING_MOVEMENT_SPEED_KMH) {
return 'movement'
}
return 'blended'
}
function getInitialBearingDeg(from: LonLatPoint, to: LonLatPoint): number {
const fromLatRad = from.lat * Math.PI / 180
const toLatRad = to.lat * Math.PI / 180
@@ -601,6 +638,7 @@ export class MapEngine {
currentGpsPoint: LonLatPoint | null
currentGpsTrack: LonLatPoint[]
currentGpsAccuracyMeters: number | null
currentGpsInsideMap: boolean
courseData: OrienteeringCourseData | null
courseOverlayVisible: boolean
cpRadiusMeters: number
@@ -626,6 +664,7 @@ export class MapEngine {
stageFxTimer: number
sessionTimerInterval: number
hasGpsCenteredOnce: boolean
gpsLockEnabled: boolean
constructor(buildVersion: string, callbacks: MapEngineCallbacks) {
this.buildVersion = buildVersion
@@ -767,6 +806,7 @@ export class MapEngine {
this.currentGpsPoint = null
this.currentGpsTrack = []
this.currentGpsAccuracyMeters = null
this.currentGpsInsideMap = false
this.courseData = null
this.courseOverlayVisible = false
this.cpRadiusMeters = 5
@@ -787,6 +827,7 @@ export class MapEngine {
this.skipRadiusMeters = 30
this.skipRequiresConfirm = true
this.autoFinishOnLastControl = true
this.gpsLockEnabled = false
this.punchFeedbackTimer = 0
this.contentCardTimer = 0
this.mapPulseTimer = 0
@@ -812,7 +853,7 @@ export class MapEngine {
sensorHeadingText: '--',
compassDeclinationText: formatCompassDeclinationText(DEFAULT_NORTH_REFERENCE_MODE),
northReferenceButtonText: formatNorthReferenceButtonText(DEFAULT_NORTH_REFERENCE_MODE),
autoRotateSourceText: formatAutoRotateSourceText('sensor', false),
autoRotateSourceText: formatAutoRotateSourceText('smart', false),
autoRotateCalibrationText: formatAutoRotateCalibrationText(false, getMapNorthOffsetDeg(DEFAULT_NORTH_REFERENCE_MODE)),
northReferenceText: formatNorthReferenceText(DEFAULT_NORTH_REFERENCE_MODE),
compassNeedleDeg: 0,
@@ -839,6 +880,8 @@ export class MapEngine {
statusText: `单 WebGL 管线已就绪,等待传感器接入 (${this.buildVersion})`,
gpsTracking: false,
gpsTrackingText: '持续定位待启动',
gpsLockEnabled: false,
gpsLockAvailable: false,
locationSourceMode: 'real',
locationSourceText: '真实定位',
mockBridgeConnected: false,
@@ -932,7 +975,7 @@ export class MapEngine {
this.autoRotateHeadingDeg = null
this.courseHeadingDeg = null
this.targetAutoRotationDeg = null
this.autoRotateSourceMode = 'sensor'
this.autoRotateSourceMode = 'smart'
this.autoRotateCalibrationOffsetDeg = getMapNorthOffsetDeg(DEFAULT_NORTH_REFERENCE_MODE)
this.autoRotateCalibrationPending = false
}
@@ -1052,6 +1095,7 @@ export class MapEngine {
this.currentGpsPoint = null
this.currentGpsTrack = []
this.currentGpsAccuracyMeters = null
this.currentGpsInsideMap = false
this.courseOverlayVisible = false
this.setCourseHeading(null)
}
@@ -1104,6 +1148,8 @@ export class MapEngine {
const debugState = this.locationController.getDebugState()
return {
gpsTracking: debugState.listening,
gpsLockEnabled: this.gpsLockEnabled,
gpsLockAvailable: !!this.currentGpsPoint && this.currentGpsInsideMap,
locationSourceMode: debugState.sourceMode,
locationSourceText: debugState.sourceModeText,
mockBridgeConnected: debugState.mockBridgeConnected,
@@ -1224,6 +1270,8 @@ export class MapEngine {
punchButtonEnabled: this.gamePresentation.hud.punchButtonEnabled,
skipButtonEnabled: this.isSkipAvailable(),
punchHintText: this.gamePresentation.hud.punchHintText,
gpsLockEnabled: this.gpsLockEnabled,
gpsLockAvailable: !!this.currentGpsPoint && this.currentGpsInsideMap,
}
if (statusText) {
@@ -1510,15 +1558,21 @@ export class MapEngine {
}
const startedAt = Date.now()
let gameResult = this.gameRuntime.startSession(startedAt)
const startResult = this.gameRuntime.startSession(startedAt)
let gameResult = startResult
if (this.currentGpsPoint) {
gameResult = this.gameRuntime.dispatch({
const gpsResult = this.gameRuntime.dispatch({
type: 'gps_updated',
at: Date.now(),
lon: this.currentGpsPoint.lon,
lat: this.currentGpsPoint.lat,
accuracyMeters: this.currentGpsAccuracyMeters,
})
gameResult = {
nextState: gpsResult.nextState,
presentation: gpsResult.presentation,
effects: [...startResult.effects, ...gpsResult.effects],
}
}
this.courseOverlayVisible = true
@@ -1534,6 +1588,7 @@ export class MapEngine {
if (!this.courseData) {
this.clearGameRuntime()
this.resetTransientGameUiState()
this.feedbackDirector.handleEffects([{ type: 'session_cancelled' }])
this.setState({
...this.getGameViewPatch(`已退出当前对局 (${this.buildVersion})`),
}, true)
@@ -1543,6 +1598,7 @@ export class MapEngine {
this.loadGameDefinitionFromCourse()
this.resetTransientGameUiState()
this.feedbackDirector.handleEffects([{ type: 'session_cancelled' }])
this.setState({
...this.getGameViewPatch(`已退出当前对局 (${this.buildVersion})`),
}, true)
@@ -1572,8 +1628,14 @@ export class MapEngine {
const gpsTileX = Math.floor(gpsWorldPoint.x)
const gpsTileY = Math.floor(gpsWorldPoint.y)
const gpsInsideMap = isTileWithinBounds(this.tileBoundsByZoom, this.state.zoom, gpsTileX, gpsTileY)
this.currentGpsInsideMap = gpsInsideMap
let gameStatusText: string | null = null
if (!gpsInsideMap && this.gpsLockEnabled) {
this.gpsLockEnabled = false
gameStatusText = `GPS已超出地图范围锁定已关闭 (${this.buildVersion})`
}
if (this.courseData) {
const eventAt = Date.now()
const gameResult = this.gameRuntime.dispatch({
@@ -1594,18 +1656,23 @@ export class MapEngine {
gameStatusText = this.resolveAppliedGameStatusText(gameResult)
}
if (gpsInsideMap && !this.hasGpsCenteredOnce) {
if (this.state.orientationMode === 'heading-up' && this.refreshAutoRotateTarget()) {
this.scheduleAutoRotate()
}
if (gpsInsideMap && (this.gpsLockEnabled || !this.hasGpsCenteredOnce)) {
this.hasGpsCenteredOnce = true
const lockedViewport = this.resolveViewportForExactCenter(gpsWorldPoint.x, gpsWorldPoint.y)
this.commitViewport({
centerTileX: gpsWorldPoint.x,
centerTileY: gpsWorldPoint.y,
tileTranslateX: 0,
tileTranslateY: 0,
...lockedViewport,
gpsTracking: true,
gpsTrackingText: '持续定位进行中',
gpsCoordText: formatGpsCoordText(nextPoint, accuracyMeters),
autoRotateSourceText: this.getAutoRotateSourceText(),
gpsLockEnabled: this.gpsLockEnabled,
gpsLockAvailable: true,
...this.getGameViewPatch(),
}, gameStatusText || `GPS定位成功已定位到当前位置 (${this.buildVersion})`, true)
}, gameStatusText || (this.gpsLockEnabled ? `GPS锁定跟随中 (${this.buildVersion})` : `GPS定位成功已定位到当前位置 (${this.buildVersion})`), true)
return
}
@@ -1613,11 +1680,62 @@ export class MapEngine {
gpsTracking: true,
gpsTrackingText: gpsInsideMap ? '持续定位进行中' : 'GPS不在当前地图范围内',
gpsCoordText: formatGpsCoordText(nextPoint, accuracyMeters),
autoRotateSourceText: this.getAutoRotateSourceText(),
gpsLockEnabled: this.gpsLockEnabled,
gpsLockAvailable: gpsInsideMap,
...this.getGameViewPatch(gameStatusText || (gpsInsideMap ? `GPS位置已更新 (${this.buildVersion})` : `GPS位置超出当前地图范围 (${this.buildVersion})`)),
}, true)
this.syncRenderer()
}
handleToggleGpsLock(): void {
if (!this.currentGpsPoint || !this.currentGpsInsideMap) {
this.setState({
gpsLockEnabled: false,
gpsLockAvailable: false,
statusText: this.currentGpsPoint
? `当前位置不在地图范围内,无法锁定 (${this.buildVersion})`
: `当前还没有可锁定的GPS位置 (${this.buildVersion})`,
}, true)
return
}
const nextEnabled = !this.gpsLockEnabled
this.gpsLockEnabled = nextEnabled
if (nextEnabled) {
const gpsWorldPoint = lonLatToWorldTile(this.currentGpsPoint, this.state.zoom)
const gpsTileX = Math.floor(gpsWorldPoint.x)
const gpsTileY = Math.floor(gpsWorldPoint.y)
const gpsInsideMap = isTileWithinBounds(this.tileBoundsByZoom, this.state.zoom, gpsTileX, gpsTileY)
if (gpsInsideMap) {
this.hasGpsCenteredOnce = true
const lockedViewport = this.resolveViewportForExactCenter(gpsWorldPoint.x, gpsWorldPoint.y)
this.commitViewport({
...lockedViewport,
gpsLockEnabled: true,
gpsLockAvailable: true,
}, `GPS已锁定在屏幕中央 (${this.buildVersion})`, true)
return
}
this.setState({
gpsLockEnabled: true,
gpsLockAvailable: true,
statusText: `GPS锁定已开启等待进入地图范围 (${this.buildVersion})`,
}, true)
this.syncRenderer()
return
}
this.setState({
gpsLockEnabled: false,
gpsLockAvailable: true,
statusText: `GPS锁定已关闭 (${this.buildVersion})`,
}, true)
this.syncRenderer()
}
handleToggleOsmReference(): void {
const nextEnabled = !this.state.osmReferenceEnabled
this.setState({
@@ -1906,13 +2024,17 @@ export class MapEngine {
this.panVelocityY = 0
if (event.touches.length >= 2) {
const origin = this.getStagePoint(event.touches)
const origin = this.gpsLockEnabled
? { x: this.state.stageWidth / 2, y: this.state.stageHeight / 2 }
: this.getStagePoint(event.touches)
this.gestureMode = 'pinch'
this.pinchStartDistance = this.getTouchDistance(event.touches)
this.pinchStartScale = this.previewScale || 1
this.pinchStartAngle = this.getTouchAngle(event.touches)
this.pinchStartRotationDeg = this.state.rotationDeg
const anchorWorld = screenToWorld(this.getCameraState(), origin, true)
const anchorWorld = this.gpsLockEnabled && this.currentGpsPoint
? lonLatToWorldTile(this.currentGpsPoint, this.state.zoom)
: screenToWorld(this.getCameraState(), origin, true)
this.pinchAnchorWorldX = anchorWorld.x
this.pinchAnchorWorldY = anchorWorld.y
this.setPreviewState(this.pinchStartScale, origin.x, origin.y)
@@ -1936,13 +2058,17 @@ export class MapEngine {
if (event.touches.length >= 2) {
const distance = this.getTouchDistance(event.touches)
const angle = this.getTouchAngle(event.touches)
const origin = this.getStagePoint(event.touches)
const origin = this.gpsLockEnabled
? { x: this.state.stageWidth / 2, y: this.state.stageHeight / 2 }
: this.getStagePoint(event.touches)
if (!this.pinchStartDistance) {
this.pinchStartDistance = distance
this.pinchStartScale = this.previewScale || 1
this.pinchStartAngle = angle
this.pinchStartRotationDeg = this.state.rotationDeg
const anchorWorld = screenToWorld(this.getCameraState(), origin, true)
const anchorWorld = this.gpsLockEnabled && this.currentGpsPoint
? lonLatToWorldTile(this.currentGpsPoint, this.state.zoom)
: screenToWorld(this.getCameraState(), origin, true)
this.pinchAnchorWorldX = anchorWorld.x
this.pinchAnchorWorldY = anchorWorld.y
}
@@ -1992,6 +2118,12 @@ export class MapEngine {
this.panLastY = touch.pageY
this.panLastTimestamp = nextTimestamp
if (this.gpsLockEnabled) {
this.panVelocityX = 0
this.panVelocityY = 0
return
}
this.normalizeTranslate(
this.state.tileTranslateX + deltaX,
this.state.tileTranslateY + deltaY,
@@ -2011,8 +2143,8 @@ export class MapEngine {
if (this.gestureMode === 'pinch' && event.touches.length < 2) {
const gestureScale = this.previewScale || 1
const zoomDelta = Math.round(Math.log2(gestureScale))
const originX = this.previewOriginX || this.state.stageWidth / 2
const originY = this.previewOriginY || this.state.stageHeight / 2
const originX = this.gpsLockEnabled ? this.state.stageWidth / 2 : (this.previewOriginX || this.state.stageWidth / 2)
const originY = this.gpsLockEnabled ? this.state.stageHeight / 2 : (this.previewOriginY || this.state.stageHeight / 2)
if (zoomDelta) {
const residualScale = gestureScale / Math.pow(2, zoomDelta)
@@ -2350,6 +2482,7 @@ export class MapEngine {
rotationToggleText: formatRotationToggleText('heading-up'),
orientationMode: 'heading-up',
orientationModeText: formatOrientationModeText('heading-up'),
autoRotateSourceText: this.getAutoRotateSourceText(),
autoRotateCalibrationText: formatAutoRotateCalibrationText(false, this.autoRotateCalibrationOffsetDeg),
northReferenceText: formatNorthReferenceText(this.northReferenceMode),
statusText: `正在启用朝向朝上模式 (${this.buildVersion})`,
@@ -2376,7 +2509,7 @@ export class MapEngine {
sensorHeadingText: formatHeadingText(compassHeadingDeg),
compassDeclinationText: formatCompassDeclinationText(this.northReferenceMode),
northReferenceButtonText: formatNorthReferenceButtonText(this.northReferenceMode),
autoRotateSourceText: formatAutoRotateSourceText(this.autoRotateSourceMode, this.courseHeadingDeg !== null),
autoRotateSourceText: this.getAutoRotateSourceText(),
compassNeedleDeg: formatCompassNeedleDegForMode(this.northReferenceMode, this.smoothedSensorHeadingDeg),
northReferenceText: formatNorthReferenceText(this.northReferenceMode),
})
@@ -2454,7 +2587,7 @@ export class MapEngine {
setCourseHeading(headingDeg: number | null): void {
this.courseHeadingDeg = headingDeg === null ? null : normalizeRotationDeg(headingDeg)
this.setState({
autoRotateSourceText: formatAutoRotateSourceText(this.autoRotateSourceMode, this.courseHeadingDeg !== null),
autoRotateSourceText: this.getAutoRotateSourceText(),
})
if (this.refreshAutoRotateTarget()) {
@@ -2462,7 +2595,72 @@ export class MapEngine {
}
}
getMovementHeadingDeg(): number | null {
if (!this.currentGpsInsideMap) {
return null
}
if (this.currentGpsAccuracyMeters !== null && this.currentGpsAccuracyMeters > SMART_HEADING_MAX_ACCURACY_METERS) {
return null
}
if (this.currentGpsTrack.length < 2) {
return null
}
const lastPoint = this.currentGpsTrack[this.currentGpsTrack.length - 1]
let accumulatedDistanceMeters = 0
for (let index = this.currentGpsTrack.length - 2; index >= 0; index -= 1) {
const nextPoint = this.currentGpsTrack[index + 1]
const point = this.currentGpsTrack[index]
accumulatedDistanceMeters += getApproxDistanceMeters(point, nextPoint)
if (accumulatedDistanceMeters >= SMART_HEADING_MIN_DISTANCE_METERS) {
return getInitialBearingDeg(point, lastPoint)
}
}
return null
}
getSmartAutoRotateHeadingDeg(): number | null {
const sensorHeadingDeg = this.smoothedSensorHeadingDeg === null
? null
: getMapReferenceHeadingDegFromSensor(this.northReferenceMode, this.smoothedSensorHeadingDeg)
const movementHeadingDeg = this.getMovementHeadingDeg()
const speedKmh = this.telemetryRuntime.state.currentSpeedKmh
const smartSource = resolveSmartHeadingSource(speedKmh, movementHeadingDeg !== null)
if (smartSource === 'movement') {
return movementHeadingDeg === null ? sensorHeadingDeg : movementHeadingDeg
}
if (smartSource === 'blended' && sensorHeadingDeg !== null && movementHeadingDeg !== null && speedKmh !== null) {
const blend = Math.max(0, Math.min(1, (speedKmh - SMART_HEADING_BLEND_START_SPEED_KMH) / (SMART_HEADING_MOVEMENT_SPEED_KMH - SMART_HEADING_BLEND_START_SPEED_KMH)))
return interpolateAngleDeg(sensorHeadingDeg, movementHeadingDeg, blend)
}
return sensorHeadingDeg === null ? movementHeadingDeg : sensorHeadingDeg
}
getAutoRotateSourceText(): string {
if (this.autoRotateSourceMode !== 'smart') {
return formatAutoRotateSourceText(this.autoRotateSourceMode, this.courseHeadingDeg !== null)
}
const smartSource = resolveSmartHeadingSource(
this.telemetryRuntime.state.currentSpeedKmh,
this.getMovementHeadingDeg() !== null,
)
return formatSmartHeadingSourceText(smartSource)
}
resolveAutoRotateInputHeadingDeg(): number | null {
if (this.autoRotateSourceMode === 'smart') {
return this.getSmartAutoRotateHeadingDeg()
}
const sensorHeadingDeg = this.smoothedSensorHeadingDeg === null
? null
: getMapReferenceHeadingDegFromSensor(this.northReferenceMode, this.smoothedSensorHeadingDeg)
@@ -2976,6 +3174,26 @@ export class MapEngine {
return
}
if (this.gpsLockEnabled && this.currentGpsPoint) {
const nextGpsWorldPoint = lonLatToWorldTile(this.currentGpsPoint, nextZoom)
const resolvedViewport = this.resolveViewportForExactCenter(nextGpsWorldPoint.x, nextGpsWorldPoint.y)
this.commitViewport(
{
zoom: nextZoom,
...resolvedViewport,
},
`缩放级别调整到 ${nextZoom}`,
true,
() => {
this.setPreviewState(residualScale, this.state.stageWidth / 2, this.state.stageHeight / 2)
this.syncRenderer()
this.compassController.start()
this.animatePreviewToRest()
},
)
return
}
if (!this.state.stageWidth || !this.state.stageHeight) {
this.commitViewport(
{

View File

@@ -73,6 +73,12 @@ export class SoundDirector {
continue
}
if (effect.type === 'session_cancelled') {
this.stopGuidanceLoop()
this.play('control_completed:finish')
continue
}
if (effect.type === 'punch_feedback' && effect.tone === 'warning') {
this.play('punch_feedback:warning')
continue

View File

@@ -3,6 +3,7 @@ import { type GamePresentationState } from '../presentation/presentationState'
export type GameEffect =
| { type: 'session_started' }
| { type: 'session_cancelled' }
| { type: 'punch_feedback'; text: string; tone: 'neutral' | 'success' | 'warning' }
| { type: 'control_completed'; controlId: string; controlKind: 'start' | 'control' | 'finish'; sequence: number | null; label: string; displayTitle: string; displayBody: string }
| { type: 'guidance_state_changed'; guidanceState: GuidanceState; controlId: string | null }

View File

@@ -51,6 +51,11 @@ export class HapticsDirector {
continue
}
if (effect.type === 'session_cancelled') {
this.trigger('session_finished')
continue
}
if (effect.type === 'punch_feedback' && effect.tone === 'warning') {
this.trigger('punch_feedback:warning')
continue

View File

@@ -189,6 +189,10 @@ export class UiEffectDirector {
if (effect.type === 'session_finished') {
this.clearPunchButtonMotion()
}
if (effect.type === 'session_cancelled') {
this.clearPunchButtonMotion()
}
}
}
}

View File

@@ -42,6 +42,7 @@ type MapPageData = MapEngineViewState & {
compassLabels: CompassLabelData[]
sideButtonMode: SideButtonMode
sideToggleIconSrc: string
sideButton2Class: string
sideButton4Class: string
sideButton11Class: string
sideButton16Class: string
@@ -49,7 +50,7 @@ type MapPageData = MapEngineViewState & {
showRightButtonGroups: boolean
showBottomDebugButton: boolean
}
const INTERNAL_BUILD_VERSION = 'map-build-207'
const INTERNAL_BUILD_VERSION = 'map-build-213'
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'
let mapEngine: MapEngine | null = null
@@ -131,13 +132,19 @@ function getSideActionButtonClass(state: SideActionButtonState): string {
return 'map-side-button map-side-button--default'
}
function buildSideButtonState(data: Pick<MapPageData, 'sideButtonMode' | 'showGameInfoPanel' | 'skipButtonEnabled' | 'gameSessionStatus'>) {
function buildSideButtonState(data: Pick<MapPageData, 'sideButtonMode' | 'showGameInfoPanel' | 'skipButtonEnabled' | 'gameSessionStatus' | 'gpsLockEnabled' | 'gpsLockAvailable'>) {
const sideButton2State: SideActionButtonState = !data.gpsLockAvailable
? 'muted'
: data.gpsLockEnabled
? 'active'
: 'default'
const sideButton4State: SideActionButtonState = data.gameSessionStatus === 'idle' ? 'default' : 'active'
const sideButton11State: SideActionButtonState = data.showGameInfoPanel ? 'active' : 'default'
const sideButton16State: SideActionButtonState = data.skipButtonEnabled ? 'default' : 'muted'
return {
sideToggleIconSrc: getSideToggleIconSrc(data.sideButtonMode),
sideButton2Class: getSideActionButtonClass(sideButton2State),
sideButton4Class: getSideActionButtonClass(sideButton4State),
sideButton11Class: getSideActionButtonClass(sideButton11State),
sideButton16Class: getSideActionButtonClass(sideButton16State),
@@ -180,6 +187,8 @@ Page({
panelProgressText: '0/0',
gameSessionStatus: 'idle',
gameModeText: '顺序赛',
gpsLockEnabled: false,
gpsLockAvailable: false,
locationSourceMode: 'real',
locationSourceText: '真实定位',
mockBridgeConnected: false,
@@ -239,6 +248,8 @@ Page({
showGameInfoPanel: false,
skipButtonEnabled: false,
gameSessionStatus: 'idle',
gpsLockEnabled: false,
gpsLockAvailable: false,
}),
} as unknown as MapPageData,
@@ -306,6 +317,8 @@ Page({
panelProgressText: '0/0',
gameSessionStatus: 'idle',
gameModeText: '顺序赛',
gpsLockEnabled: false,
gpsLockAvailable: false,
locationSourceMode: 'real',
locationSourceText: '真实定位',
mockBridgeConnected: false,
@@ -363,6 +376,8 @@ Page({
showGameInfoPanel: false,
skipButtonEnabled: false,
gameSessionStatus: 'idle',
gpsLockEnabled: false,
gpsLockAvailable: false,
}),
})
},
@@ -723,9 +738,21 @@ Page({
},
handleForceExitGame() {
if (mapEngine) {
mapEngine.handleForceExitGame()
if (!mapEngine || this.data.gameSessionStatus === 'idle') {
return
}
wx.showModal({
title: '确认退出',
content: '确认强制结束当前对局并返回开始前状态?',
confirmText: '确认退出',
cancelText: '取消',
success: (result) => {
if (result.confirm && mapEngine) {
mapEngine.handleForceExitGame()
}
},
})
},
handleSkipAction() {
@@ -781,6 +808,8 @@ Page({
showGameInfoPanel: true,
skipButtonEnabled: this.data.skipButtonEnabled,
gameSessionStatus: this.data.gameSessionStatus,
gpsLockEnabled: this.data.gpsLockEnabled,
gpsLockAvailable: this.data.gpsLockAvailable,
}),
})
},
@@ -793,6 +822,8 @@ Page({
showGameInfoPanel: false,
skipButtonEnabled: this.data.skipButtonEnabled,
gameSessionStatus: this.data.gameSessionStatus,
gpsLockEnabled: this.data.gpsLockEnabled,
gpsLockAvailable: this.data.gpsLockAvailable,
}),
})
},
@@ -832,9 +863,16 @@ Page({
showGameInfoPanel: this.data.showGameInfoPanel,
skipButtonEnabled: this.data.skipButtonEnabled,
gameSessionStatus: this.data.gameSessionStatus,
gpsLockEnabled: this.data.gpsLockEnabled,
gpsLockAvailable: this.data.gpsLockAvailable,
}),
})
},
handleToggleGpsLock() {
if (mapEngine) {
mapEngine.handleToggleGpsLock()
}
},
handleToggleMapRotateMode() {
if (!mapEngine) {
return
@@ -856,6 +894,8 @@ Page({
showGameInfoPanel: false,
skipButtonEnabled: this.data.skipButtonEnabled,
gameSessionStatus: this.data.gameSessionStatus,
gpsLockEnabled: this.data.gpsLockEnabled,
gpsLockAvailable: this.data.gpsLockAvailable,
}),
})
},
@@ -868,6 +908,8 @@ Page({
showGameInfoPanel: this.data.showGameInfoPanel,
skipButtonEnabled: this.data.skipButtonEnabled,
gameSessionStatus: this.data.gameSessionStatus,
gpsLockEnabled: this.data.gpsLockEnabled,
gpsLockAvailable: this.data.gpsLockAvailable,
}),
})
},

View File

@@ -25,7 +25,6 @@
></canvas>
</view>
<view class="map-stage__crosshair"></view>
<view class="map-stage__map-pulse {{mapPulseFxClass}}" wx:if="{{mapPulseVisible}}" style="left: {{mapPulseLeftPx}}px; top: {{mapPulseTopPx}}px;"></view>
<view class="map-stage__stage-fx {{stageFxClass}}" wx:if="{{stageFxVisible}}"></view>
@@ -77,7 +76,7 @@
<cover-view class="map-side-column map-side-column--left map-side-column--left-group" wx:if="{{!showDebugPanel && !showGameInfoPanel && showLeftButtonGroup}}" style="top: {{topInsetHeight}}px;">
<cover-view class="map-side-button map-side-button--icon" bindtap="handleToggleMapRotateMode"><cover-image class="map-side-button__rotate-image {{orientationMode === 'heading-up' ? 'map-side-button__rotate-image--active' : ''}}" src="../../assets/btn_map_rotate_cropped.png"></cover-image></cover-view>
<cover-view class="map-side-button map-side-button--muted"><cover-view class="map-side-button__text">1</cover-view></cover-view>
<cover-view class="map-side-button"><cover-view class="map-side-button__text">2</cover-view></cover-view>
<cover-view class="{{sideButton2Class}}" bindtap="handleToggleGpsLock"><cover-view class="map-side-button__text">2</cover-view></cover-view>
<cover-view class="map-side-button map-side-button--active"><cover-view class="map-side-button__text">3</cover-view></cover-view>
<cover-view class="{{sideButton4Class}}" bindtap="handleForceExitGame"><cover-image class="map-side-button__action-image" src="../../assets/btn_exit.png"></cover-image></cover-view>
</cover-view>

View File

@@ -51,43 +51,6 @@
pointer-events: none;
}
.map-stage__crosshair {
position: absolute;
left: 50%;
top: 50%;
width: 44rpx;
height: 44rpx;
transform: translate(-50%, -50%);
border: 3rpx solid rgba(255, 255, 255, 0.95);
border-radius: 50%;
box-shadow: 0 0 0 4rpx rgba(22, 48, 32, 0.2);
pointer-events: none;
z-index: 3;
}
.map-stage__crosshair::before,
.map-stage__crosshair::after {
content: '';
position: absolute;
background: rgba(255, 255, 255, 0.95);
}
.map-stage__crosshair::before {
left: 50%;
top: -18rpx;
width: 2rpx;
height: 76rpx;
transform: translateX(-50%);
}
.map-stage__crosshair::after {
left: -18rpx;
top: 50%;
width: 76rpx;
height: 2rpx;
transform: translateY(-50%);
}
.map-stage__map-pulse {
position: absolute;
width: 44rpx;