Refine compass and ruler overlay

This commit is contained in:
2026-03-25 19:47:39 +08:00
parent f7d4499e36
commit ce25530938
5 changed files with 534 additions and 14 deletions

View File

@@ -0,0 +1,96 @@
# 沟通协作建议
这份文档用于约定后续在 UI 微调、交互细改、规则补充时,怎样沟通最有效,减少来回修改。
## 1. 需求描述的推荐格式
后续尽量按下面 4 个点描述需求:
### 改动对象
明确指出这次只改什么。
例如:
- 比例尺和指北针顶部数字之间的距离
- 某个按钮的高亮状态
- 某条提示文案
### 不要改
明确指出哪些相邻元素不要动。
例如:
- 不要改顶部数字和小箭头之间的距离
- 不要动比例尺刻度算法
- 不要改地图引擎逻辑
### 目标效果
说明你想达到什么视觉或交互结果。
例如:
- 更靠近一点
- 改成 4 到 5 像素的空隙
- 只在可点击时高亮
### 验证标准
说明你用什么标准判断“改对了”。
例如:
- 看起来贴近,但不要重叠
- 指北针仍然盖住比例尺
- 缩放时要实时变化,不要手势结束后再跳
## 2. 推荐的需求表达模板
可以直接按这个模板发:
```text
改动对象:
不要改:
目标:
验证标准:
```
例如:
```text
改动对象:比例尺和顶部角度数字之间的距离
不要改:顶部角度数字和小箭头之间的距离
目标:再近一点
验证标准:看起来大约 4~5px不重叠
```
## 3. 为什么这样最有效
很多来回修改,通常不是功能做不了,而是:
- 一次需求里混了两层甚至三层改动
- 没说清楚“哪块不要动”
- 验收标准只有感觉,没有边界
一旦把“改什么”和“不要改什么”拆开,误改概率会明显下降。
## 4. 开发执行的约定
后续默认按下面方式执行:
- 先复述这次只改哪一层
- 真实执行时只改这一层
- 不顺手带改别的部分
- 改完明确说明:
- 改了什么
- 没改什么
## 5. 最适合哪些场景
这套方式尤其适合:
- UI 间距微调
- 按钮状态和图标切换
- HUD 显示调整
- 地图表现修正
- 规则字段补充
## 6. 一句话原则
后续最有效的协作方式是:
**需求把边界说死,修改一次只动一层。**

View File

@@ -23,7 +23,7 @@ const RENDER_MODE = 'Single WebGL Pipeline'
const PROJECTION_MODE = 'WGS84 -> WorldTile -> Camera -> Screen'
const MAP_NORTH_OFFSET_DEG = 0
let MAGNETIC_DECLINATION_DEG = -6.91
let MAGNETIC_DECLINATION_TEXT = '6.91 W'
let MAGNETIC_DECLINATION_TEXT = '6.91˚ W'
const MIN_ZOOM = 15
const MAX_ZOOM = 20
const DEFAULT_ZOOM = 17
@@ -129,6 +129,7 @@ export interface MapEngineViewState {
tileTranslateX: number
tileTranslateY: number
tileSizePx: number
previewScale: number
stageWidth: number
stageHeight: number
stageLeft: number
@@ -232,6 +233,8 @@ const VIEW_SYNC_KEYS: Array<keyof MapEngineViewState> = [
'mapName',
'configStatusText',
'zoom',
'centerTileX',
'centerTileY',
'rotationDeg',
'rotationText',
'rotationMode',
@@ -263,6 +266,11 @@ const VIEW_SYNC_KEYS: Array<keyof MapEngineViewState> = [
'networkFetchCount',
'cacheHitRateText',
'tileSizePx',
'previewScale',
'stageWidth',
'stageHeight',
'stageLeft',
'stageTop',
'statusText',
'gpsTracking',
'gpsTrackingText',
@@ -393,12 +401,16 @@ function formatRotationText(rotationDeg: number): string {
return `${Math.round(normalizeRotationDeg(rotationDeg))}deg`
}
function normalizeDegreeDisplayText(text: string): string {
return text.replace(/[°掳•]/g, '˚')
}
function formatHeadingText(headingDeg: number | null): string {
if (headingDeg === null) {
return '--'
}
return `${Math.round(normalizeRotationDeg(headingDeg))}`
return `${Math.round(normalizeRotationDeg(headingDeg))}˚`
}
function formatDevicePoseText(pose: 'upright' | 'tilted' | 'flat'): string {
@@ -1011,6 +1023,7 @@ export class MapEngine {
tileTranslateX: 0,
tileTranslateY: 0,
tileSizePx: 0,
previewScale: 1,
stageWidth: 0,
stageHeight: 0,
stageLeft: 0,
@@ -2114,7 +2127,7 @@ export class MapEngine {
applyRemoteMapConfig(config: RemoteMapConfig): void {
MAGNETIC_DECLINATION_DEG = config.magneticDeclinationDeg
MAGNETIC_DECLINATION_TEXT = config.magneticDeclinationText
MAGNETIC_DECLINATION_TEXT = normalizeDegreeDisplayText(config.magneticDeclinationText)
this.minZoom = config.minZoom
this.maxZoom = config.maxZoom
this.defaultZoom = config.defaultZoom
@@ -3168,6 +3181,9 @@ export class MapEngine {
this.previewScale = scale
this.previewOriginX = originX
this.previewOriginY = originY
this.setState({
previewScale: scale,
}, true)
}
resetPreviewState(): void {

View File

@@ -18,11 +18,24 @@ type CompassLabelData = {
radius: number
className: string
}
type ScaleRulerMinorTickData = {
key: string
topPx: number
long: boolean
}
type ScaleRulerMajorMarkData = {
key: string
topPx: number
label: string
}
type SideButtonMode = 'all' | 'left' | 'right' | 'hidden'
type SideActionButtonState = 'muted' | 'default' | 'active'
type CenterScaleRulerAnchorMode = 'screen-center' | 'compass-center'
type MapPageData = MapEngineViewState & {
showDebugPanel: boolean
showGameInfoPanel: boolean
showCenterScaleRuler: boolean
centerScaleRulerAnchorMode: CenterScaleRulerAnchorMode
statusBarHeight: number
topInsetHeight: number
hudPanelIndex: number
@@ -45,12 +58,23 @@ type MapPageData = MapEngineViewState & {
sideButton2Class: string
sideButton4Class: string
sideButton11Class: string
sideButton13Class: string
sideButton14Class: string
sideButton16Class: string
centerScaleRulerVisible: boolean
centerScaleRulerCenterXPx: number
centerScaleRulerZeroYPx: number
centerScaleRulerHeightPx: number
centerScaleRulerAxisBottomPx: number
centerScaleRulerZeroVisible: boolean
centerScaleRulerZeroLabel: string
centerScaleRulerMinorTicks: ScaleRulerMinorTickData[]
centerScaleRulerMajorMarks: ScaleRulerMajorMarkData[]
showLeftButtonGroup: boolean
showRightButtonGroups: boolean
showBottomDebugButton: boolean
}
const INTERNAL_BUILD_VERSION = 'map-build-232'
const INTERNAL_BUILD_VERSION = 'map-build-252'
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
@@ -132,7 +156,7 @@ function getSideActionButtonClass(state: SideActionButtonState): string {
return 'map-side-button map-side-button--default'
}
function buildSideButtonState(data: Pick<MapPageData, 'sideButtonMode' | 'showGameInfoPanel' | 'skipButtonEnabled' | 'gameSessionStatus' | 'gpsLockEnabled' | 'gpsLockAvailable'>) {
function buildSideButtonState(data: Pick<MapPageData, 'sideButtonMode' | 'showGameInfoPanel' | 'showCenterScaleRuler' | 'centerScaleRulerAnchorMode' | 'skipButtonEnabled' | 'gameSessionStatus' | 'gpsLockEnabled' | 'gpsLockAvailable'>) {
const sideButton2State: SideActionButtonState = !data.gpsLockAvailable
? 'muted'
: data.gpsLockEnabled
@@ -140,6 +164,12 @@ function buildSideButtonState(data: Pick<MapPageData, 'sideButtonMode' | 'showGa
: 'default'
const sideButton4State: SideActionButtonState = data.gameSessionStatus === 'idle' ? 'default' : 'active'
const sideButton11State: SideActionButtonState = data.showGameInfoPanel ? 'active' : 'default'
const sideButton13State: SideActionButtonState = data.showCenterScaleRuler ? 'active' : 'default'
const sideButton14State: SideActionButtonState = !data.showCenterScaleRuler
? 'muted'
: data.centerScaleRulerAnchorMode === 'compass-center'
? 'active'
: 'default'
const sideButton16State: SideActionButtonState = data.skipButtonEnabled ? 'default' : 'muted'
return {
@@ -147,10 +177,182 @@ function buildSideButtonState(data: Pick<MapPageData, 'sideButtonMode' | 'showGa
sideButton2Class: getSideActionButtonClass(sideButton2State),
sideButton4Class: getSideActionButtonClass(sideButton4State),
sideButton11Class: getSideActionButtonClass(sideButton11State),
sideButton13Class: getSideActionButtonClass(sideButton13State),
sideButton14Class: getSideActionButtonClass(sideButton14State),
sideButton16Class: getSideActionButtonClass(sideButton16State),
}
}
function getRpxUnitInPx(): number {
const systemInfo = wx.getSystemInfoSync()
return systemInfo.windowWidth / 750
}
function worldTileYToLat(worldTileY: number, zoom: number): number {
const scale = Math.pow(2, zoom)
const n = Math.PI - (2 * Math.PI * worldTileY) / scale
return (180 / Math.PI) * Math.atan(Math.sinh(n))
}
function getNiceDistanceMeters(rawDistanceMeters: number): number {
if (!Number.isFinite(rawDistanceMeters) || rawDistanceMeters <= 0) {
return 50
}
const exponent = Math.floor(Math.log10(rawDistanceMeters))
const base = Math.pow(10, exponent)
const normalized = rawDistanceMeters / base
if (normalized <= 1) {
return base
}
if (normalized <= 2) {
return 2 * base
}
if (normalized <= 5) {
return 5 * base
}
return 10 * base
}
function formatScaleDistanceLabel(distanceMeters: number): string {
if (distanceMeters >= 1000) {
const distanceKm = distanceMeters / 1000
const formatted = distanceKm >= 10 ? distanceKm.toFixed(0) : distanceKm.toFixed(1)
return `${formatted.replace(/\.0$/, '')} km`
}
return `${Math.round(distanceMeters)} m`
}
function buildCenterScaleRulerPatch(data: Pick<MapPageData, 'showCenterScaleRuler' | 'centerScaleRulerAnchorMode' | 'stageWidth' | 'stageHeight' | 'topInsetHeight' | 'zoom' | 'centerTileY' | 'tileSizePx' | 'previewScale'>) {
if (!data.showCenterScaleRuler) {
return {
centerScaleRulerVisible: false,
centerScaleRulerCenterXPx: 0,
centerScaleRulerZeroYPx: 0,
centerScaleRulerHeightPx: 0,
centerScaleRulerAxisBottomPx: 0,
centerScaleRulerZeroVisible: false,
centerScaleRulerZeroLabel: '0 m',
centerScaleRulerMinorTicks: [] as ScaleRulerMinorTickData[],
centerScaleRulerMajorMarks: [] as ScaleRulerMajorMarkData[],
}
}
if (!data.stageWidth || !data.stageHeight) {
return {
centerScaleRulerVisible: false,
centerScaleRulerCenterXPx: 0,
centerScaleRulerZeroYPx: 0,
centerScaleRulerHeightPx: 0,
centerScaleRulerAxisBottomPx: 0,
centerScaleRulerZeroVisible: false,
centerScaleRulerZeroLabel: '0 m',
centerScaleRulerMinorTicks: [] as ScaleRulerMinorTickData[],
centerScaleRulerMajorMarks: [] as ScaleRulerMajorMarkData[],
}
}
const topPadding = 12
const rpxUnitPx = getRpxUnitInPx()
const compassBottomPaddingPx = 248 * rpxUnitPx
const compassDialRadiusPx = (196 * rpxUnitPx) / 2
const compassHeadingOverlayHeightPx = 40 * rpxUnitPx
const compassOcclusionPaddingPx = 10 * rpxUnitPx
const zeroYPx = data.centerScaleRulerAnchorMode === 'compass-center'
? Math.round(data.stageHeight - compassBottomPaddingPx - compassDialRadiusPx)
: Math.round(data.stageHeight / 2)
const fallbackHeight = Math.max(zeroYPx - topPadding, 160)
const coveredBottomPx = data.centerScaleRulerAnchorMode === 'compass-center'
? Math.round(compassDialRadiusPx + compassHeadingOverlayHeightPx + compassOcclusionPaddingPx)
: 0
if (
!data.tileSizePx
|| !Number.isFinite(data.zoom)
|| !Number.isFinite(data.centerTileY)
) {
return {
centerScaleRulerVisible: true,
centerScaleRulerCenterXPx: Math.round(data.stageWidth / 2),
centerScaleRulerZeroYPx: zeroYPx,
centerScaleRulerHeightPx: fallbackHeight,
centerScaleRulerAxisBottomPx: coveredBottomPx,
centerScaleRulerZeroVisible: data.centerScaleRulerAnchorMode !== 'compass-center',
centerScaleRulerZeroLabel: '0 m',
centerScaleRulerMinorTicks: [] as ScaleRulerMinorTickData[],
centerScaleRulerMajorMarks: [] as ScaleRulerMajorMarkData[],
}
}
const centerLat = worldTileYToLat(data.centerTileY + 0.5, data.zoom)
const metersPerTile = Math.cos(centerLat * Math.PI / 180) * 40075016.686 / Math.pow(2, data.zoom)
const metersPerPixel = metersPerTile / data.tileSizePx
const effectivePreviewScale = Number.isFinite(data.previewScale) && data.previewScale > 0 ? data.previewScale : 1
const effectiveMetersPerPixel = metersPerPixel / effectivePreviewScale
const rulerHeight = Math.floor(zeroYPx - topPadding)
if (!Number.isFinite(effectiveMetersPerPixel) || effectiveMetersPerPixel <= 0 || rulerHeight < 120) {
return {
centerScaleRulerVisible: true,
centerScaleRulerCenterXPx: Math.round(data.stageWidth / 2),
centerScaleRulerZeroYPx: zeroYPx,
centerScaleRulerHeightPx: Math.max(rulerHeight, fallbackHeight),
centerScaleRulerAxisBottomPx: coveredBottomPx,
centerScaleRulerZeroVisible: data.centerScaleRulerAnchorMode !== 'compass-center',
centerScaleRulerZeroLabel: '0 m',
centerScaleRulerMinorTicks: [] as ScaleRulerMinorTickData[],
centerScaleRulerMajorMarks: [] as ScaleRulerMajorMarkData[],
}
}
const labelDistanceMeters = getNiceDistanceMeters(effectiveMetersPerPixel * 80)
const minorDistanceMeters = labelDistanceMeters / 8
const minorStepPx = minorDistanceMeters / effectiveMetersPerPixel
const visibleTopLimitPx = rulerHeight - coveredBottomPx
const minorTicks: ScaleRulerMinorTickData[] = []
const majorMarks: ScaleRulerMajorMarkData[] = []
for (let index = 1; index <= 200; index += 1) {
const topPx = Math.round(rulerHeight - index * minorStepPx)
if (topPx < 0) {
break
}
if (topPx >= visibleTopLimitPx) {
continue
}
const isHalfMajor = index % 4 === 0
const isLabelMajor = index % 8 === 0
minorTicks.push({
key: `minor-${index}`,
topPx,
long: isHalfMajor,
})
if (isLabelMajor) {
majorMarks.push({
key: `major-${index}`,
topPx,
label: formatScaleDistanceLabel((index / 8) * labelDistanceMeters),
})
}
}
return {
centerScaleRulerVisible: true,
centerScaleRulerCenterXPx: Math.round(data.stageWidth / 2),
centerScaleRulerZeroYPx: zeroYPx,
centerScaleRulerHeightPx: rulerHeight,
centerScaleRulerAxisBottomPx: coveredBottomPx,
centerScaleRulerZeroVisible: data.centerScaleRulerAnchorMode !== 'compass-center',
centerScaleRulerZeroLabel: '0 m',
centerScaleRulerMinorTicks: minorTicks,
centerScaleRulerMajorMarks: majorMarks,
}
}
function buildEmptyGameInfoSnapshot(): MapEngineGameInfoSnapshot {
return {
title: '当前游戏',
@@ -170,10 +372,12 @@ Page({
data: {
showDebugPanel: false,
showGameInfoPanel: false,
showCenterScaleRuler: false,
statusBarHeight: 0,
topInsetHeight: 12,
hudPanelIndex: 0,
configSourceText: '顺序赛配置',
centerScaleRulerAnchorMode: 'screen-center',
gameInfoTitle: '当前游戏',
gameInfoSubtitle: '未开始',
gameInfoLocalRows: [],
@@ -246,12 +450,23 @@ Page({
mapPulseFxClass: '',
stageFxVisible: false,
stageFxClass: '',
centerScaleRulerVisible: false,
centerScaleRulerCenterXPx: 0,
centerScaleRulerZeroYPx: 0,
centerScaleRulerHeightPx: 0,
centerScaleRulerAxisBottomPx: 0,
centerScaleRulerZeroVisible: false,
centerScaleRulerZeroLabel: '0 m',
centerScaleRulerMinorTicks: [],
centerScaleRulerMajorMarks: [],
compassTicks: buildCompassTicks(),
compassLabels: buildCompassLabels(),
...buildSideButtonVisibility('left'),
...buildSideButtonState({
sideButtonMode: 'left',
showGameInfoPanel: false,
showCenterScaleRuler: false,
centerScaleRulerAnchorMode: 'screen-center',
skipButtonEnabled: false,
gameSessionStatus: 'idle',
gpsLockEnabled: false,
@@ -298,6 +513,7 @@ Page({
this.setData({
...nextData,
...buildCenterScaleRulerPatch(mergedData),
...buildSideButtonState(mergedData),
})
@@ -391,11 +607,24 @@ Page({
...buildSideButtonState({
sideButtonMode: 'left',
showGameInfoPanel: false,
showCenterScaleRuler: false,
centerScaleRulerAnchorMode: 'screen-center',
skipButtonEnabled: false,
gameSessionStatus: 'idle',
gpsLockEnabled: false,
gpsLockAvailable: false,
}),
...buildCenterScaleRulerPatch({
...(mapEngine.getInitialData() as MapPageData),
showCenterScaleRuler: false,
centerScaleRulerAnchorMode: 'screen-center',
stageWidth: 0,
stageHeight: 0,
topInsetHeight: Math.max(statusBarHeight + 12, menuButtonBottom + 20),
zoom: 0,
centerTileY: 0,
tileSizePx: 0,
}),
})
},
@@ -807,10 +1036,19 @@ Page({
}
const snapshot = mapEngine.getGameInfoSnapshot()
const localRows = snapshot.localRows.concat([
{ label: '比例尺开关', value: this.data.showCenterScaleRuler ? '开启' : '关闭' },
{ label: '比例尺锚点', value: this.data.centerScaleRulerAnchorMode === 'compass-center' ? '指北针圆心' : '屏幕中心' },
{ label: '比例尺可见', value: this.data.centerScaleRulerVisible ? 'true' : 'false' },
{ label: '比例尺中心X', value: `${this.data.centerScaleRulerCenterXPx}px` },
{ label: '比例尺零点Y', value: `${this.data.centerScaleRulerZeroYPx}px` },
{ label: '比例尺高度', value: `${this.data.centerScaleRulerHeightPx}px` },
{ label: '比例尺主刻度数', value: String(this.data.centerScaleRulerMajorMarks.length) },
])
this.setData({
gameInfoTitle: snapshot.title,
gameInfoSubtitle: snapshot.subtitle,
gameInfoLocalRows: snapshot.localRows,
gameInfoLocalRows: localRows,
gameInfoGlobalRows: snapshot.globalRows,
})
},
@@ -823,6 +1061,8 @@ Page({
...buildSideButtonState({
sideButtonMode: this.data.sideButtonMode,
showGameInfoPanel: true,
showCenterScaleRuler: this.data.showCenterScaleRuler,
centerScaleRulerAnchorMode: this.data.centerScaleRulerAnchorMode,
skipButtonEnabled: this.data.skipButtonEnabled,
gameSessionStatus: this.data.gameSessionStatus,
gpsLockEnabled: this.data.gpsLockEnabled,
@@ -837,6 +1077,8 @@ Page({
...buildSideButtonState({
sideButtonMode: this.data.sideButtonMode,
showGameInfoPanel: false,
showCenterScaleRuler: this.data.showCenterScaleRuler,
centerScaleRulerAnchorMode: this.data.centerScaleRulerAnchorMode,
skipButtonEnabled: this.data.skipButtonEnabled,
gameSessionStatus: this.data.gameSessionStatus,
gpsLockEnabled: this.data.gpsLockEnabled,
@@ -878,6 +1120,8 @@ Page({
...buildSideButtonState({
sideButtonMode: nextMode,
showGameInfoPanel: this.data.showGameInfoPanel,
showCenterScaleRuler: this.data.showCenterScaleRuler,
centerScaleRulerAnchorMode: this.data.centerScaleRulerAnchorMode,
skipButtonEnabled: this.data.skipButtonEnabled,
gameSessionStatus: this.data.gameSessionStatus,
gpsLockEnabled: this.data.gpsLockEnabled,
@@ -909,6 +1153,8 @@ Page({
...buildSideButtonState({
sideButtonMode: this.data.sideButtonMode,
showGameInfoPanel: false,
showCenterScaleRuler: this.data.showCenterScaleRuler,
centerScaleRulerAnchorMode: this.data.centerScaleRulerAnchorMode,
skipButtonEnabled: this.data.skipButtonEnabled,
gameSessionStatus: this.data.gameSessionStatus,
gpsLockEnabled: this.data.gpsLockEnabled,
@@ -923,6 +1169,8 @@ Page({
...buildSideButtonState({
sideButtonMode: this.data.sideButtonMode,
showGameInfoPanel: this.data.showGameInfoPanel,
showCenterScaleRuler: this.data.showCenterScaleRuler,
centerScaleRulerAnchorMode: this.data.centerScaleRulerAnchorMode,
skipButtonEnabled: this.data.skipButtonEnabled,
gameSessionStatus: this.data.gameSessionStatus,
gpsLockEnabled: this.data.gpsLockEnabled,
@@ -931,6 +1179,46 @@ Page({
})
},
handleToggleCenterScaleRuler() {
const nextEnabled = !this.data.showCenterScaleRuler
this.data.showCenterScaleRuler = nextEnabled
const mergedData = {
...this.data,
showCenterScaleRuler: nextEnabled,
} as MapPageData
this.setData({
showCenterScaleRuler: nextEnabled,
...buildCenterScaleRulerPatch(mergedData),
...buildSideButtonState(mergedData),
})
},
handleToggleCenterScaleRulerAnchor() {
if (!this.data.showCenterScaleRuler) {
return
}
const nextAnchorMode: CenterScaleRulerAnchorMode = this.data.centerScaleRulerAnchorMode === 'screen-center'
? 'compass-center'
: 'screen-center'
this.data.centerScaleRulerAnchorMode = nextAnchorMode
const mergedData = {
...this.data,
centerScaleRulerAnchorMode: nextAnchorMode,
} as MapPageData
this.setData({
centerScaleRulerAnchorMode: nextAnchorMode,
...buildCenterScaleRulerPatch(mergedData),
...buildSideButtonState(mergedData),
})
if (this.data.showGameInfoPanel) {
this.syncGameInfoPanelSnapshot()
}
},
handleDebugPanelTap() {},
})

View File

@@ -37,11 +37,25 @@
</view>
<view class="map-stage__overlay-center-layer" wx:if="{{!showDebugPanel && !showGameInfoPanel}}">
<view class="center-scale-ruler" wx:if="{{centerScaleRulerVisible}}" style="left: {{centerScaleRulerCenterXPx}}px; top: {{centerScaleRulerZeroYPx}}px; height: {{centerScaleRulerHeightPx}}px;">
<view class="center-scale-ruler__axis" style="bottom: {{centerScaleRulerAxisBottomPx}}px;"></view>
<view class="center-scale-ruler__arrow"></view>
<view wx:if="{{centerScaleRulerZeroVisible}}" class="center-scale-ruler__tick center-scale-ruler__tick--major center-scale-ruler__tick--zero" style="top: {{centerScaleRulerHeightPx}}px;"></view>
<view wx:if="{{centerScaleRulerZeroVisible}}" class="center-scale-ruler__label center-scale-ruler__label--zero" style="top: {{centerScaleRulerHeightPx}}px;">{{centerScaleRulerZeroLabel}}</view>
<view wx:for="{{centerScaleRulerMinorTicks}}" wx:key="key" class="center-scale-ruler__tick {{item.long ? 'center-scale-ruler__tick--major' : ''}}" style="top: {{item.topPx}}px;"></view>
<view wx:for="{{centerScaleRulerMajorMarks}}" wx:key="key" class="center-scale-ruler__label" style="top: {{item.topPx}}px;">{{item.label}}</view>
</view>
</view>
<view class="map-stage__overlay">
<view class="map-stage__bottom">
<view class="compass-widget">
<view class="compass-widget__heading">{{sensorHeadingText}}</view>
<view class="compass-widget__edge-arrow"></view>
<view class="compass-widget__heading-wrap">
<view class="compass-widget__heading">{{sensorHeadingText}}</view>
<view class="compass-widget__edge-arrow-wrap">
<view class="compass-widget__edge-arrow"></view>
</view>
</view>
<view class="compass-widget__dial {{orientationMode === 'heading-up' ? 'compass-widget__dial--active' : ''}}">
<view class="compass-widget__glass"></view>
<view class="compass-widget__inner-shadow"></view>
@@ -93,8 +107,8 @@
<cover-view class="map-side-column map-side-column--right-sub" wx:if="{{!showDebugPanel && !showGameInfoPanel && showRightButtonGroups}}" style="top: {{topInsetHeight}}px;">
<cover-view class="{{sideButton11Class}}" bindtap="handleOpenGameInfoPanel"><cover-image class="map-side-button__action-image" src="../../assets/btn_info.png"></cover-image></cover-view>
<cover-view class="map-side-button"><cover-view class="map-side-button__text">12</cover-view></cover-view>
<cover-view class="map-side-button"><cover-view class="map-side-button__text">13</cover-view></cover-view>
<cover-view class="map-side-button"><cover-view class="map-side-button__text">14</cover-view></cover-view>
<cover-view class="{{sideButton13Class}}" bindtap="handleToggleCenterScaleRuler"><cover-view class="map-side-button__text">13</cover-view></cover-view>
<cover-view class="{{sideButton14Class}}" bindtap="handleToggleCenterScaleRulerAnchor"><cover-view class="map-side-button__text">14</cover-view></cover-view>
<cover-view class="map-side-button"><cover-view class="map-side-button__text">15</cover-view></cover-view>
<cover-view class="{{sideButton16Class}}" bindtap="handleSkipAction"><cover-image class="map-side-button__action-image" src="../../assets/btn_skip_cp.png"></cover-image></cover-view>
</cover-view>

View File

@@ -97,6 +97,86 @@
z-index: 4;
}
.map-stage__overlay-center-layer {
position: absolute;
inset: 0;
pointer-events: none;
z-index: 4;
}
.center-scale-ruler {
position: absolute;
width: 220rpx;
transform: translate(-50%, -100%);
pointer-events: none;
z-index: 12;
}
.center-scale-ruler__axis {
position: absolute;
left: 50%;
top: 0;
bottom: 0;
width: 4rpx;
margin-left: -2rpx;
border-radius: 999rpx;
background: rgba(19, 20, 18, 0.92);
}
.center-scale-ruler__arrow {
position: absolute;
left: 50%;
top: -34rpx;
width: 0;
height: 0;
margin-left: -14rpx;
border-left: 14rpx solid transparent;
border-right: 14rpx solid transparent;
border-bottom: 34rpx solid rgba(19, 20, 18, 0.96);
}
.center-scale-ruler__tick {
position: absolute;
left: 50%;
width: 18rpx;
height: 4rpx;
margin-left: -9rpx;
margin-top: -2rpx;
border-radius: 999rpx;
background: rgba(19, 20, 18, 0.82);
}
.center-scale-ruler__tick--major {
width: 32rpx;
margin-left: -16rpx;
height: 5rpx;
margin-top: -2.5rpx;
background: rgba(12, 14, 12, 0.96);
}
.center-scale-ruler__tick--zero {
width: 34rpx;
margin-left: -17rpx;
}
.center-scale-ruler__label {
position: absolute;
left: 50%;
margin-left: 18rpx;
transform: translateY(-50%);
font-size: 22rpx;
line-height: 1;
font-weight: 800;
color: rgba(19, 20, 18, 0.96);
text-shadow: 0 1rpx 0 rgba(255, 255, 255, 0.28);
white-space: nowrap;
}
.center-scale-ruler__label--zero {
transform: translateY(-24%);
font-size: 24rpx;
}
.map-stage__topbar {
display: flex;
align-items: flex-start;
@@ -329,14 +409,38 @@
align-items: center;
gap: 6rpx;
flex-shrink: 0;
position: relative;
z-index: 20;
}
.compass-widget__heading-wrap {
display: flex;
flex-direction: column;
align-items: center;
gap: 0;
position: relative;
z-index: 21;
}
.compass-widget__heading {
font-size: 14rpx;
min-width: 96rpx;
padding: 6rpx 12rpx;
font-size: 20rpx;
line-height: 1;
font-weight: 600;
color: rgba(32, 42, 34, 0.72);
text-shadow: 0 1rpx 0 rgba(255, 255, 255, 0.35);
text-align: center;
}
.compass-widget__edge-arrow-wrap {
display: flex;
align-items: center;
justify-content: center;
width: 40rpx;
height: 20rpx;
margin-top: -2rpx;
margin-bottom: -4rpx;
}
.compass-widget__dial {
@@ -519,8 +623,6 @@
.compass-widget__edge-arrow {
width: 0;
height: 0;
margin-top: -2rpx;
margin-bottom: -4rpx;
border-left: 8rpx solid transparent;
border-right: 8rpx solid transparent;
border-top: 14rpx solid rgba(58, 49, 37, 0.72);
@@ -530,12 +632,16 @@
.compass-widget__hint {
max-width: 196rpx;
font-size: 14rpx;
padding: 4rpx 10rpx;
border-radius: 999rpx;
background: rgba(219, 238, 212, 0.94);
font-size: 16rpx;
line-height: 1.3;
color: #d62828;
text-align: center;
font-weight: 700;
text-shadow: 0 1rpx 0 rgba(255, 255, 255, 0.24);
box-shadow: 0 2rpx 8rpx rgba(22, 48, 32, 0.08);
}
.race-panel-swiper {
position: absolute;