fix: 修复打砖块游戏碰撞穿透bug,添加渐进提速机制
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import type { HudSummary } from "$lib/types/hud";
|
||||
|
||||
export let summary: HudSummary;
|
||||
@@ -6,6 +7,25 @@
|
||||
export let panelIndex = 0;
|
||||
export let xValues: number[] | null = null;
|
||||
export let yValues: number[] | null = null;
|
||||
export let locale: "zh-CN" | "en-US" = "zh-CN";
|
||||
export let sessionStartedAt: number = Date.now();
|
||||
export let isRealtime = false;
|
||||
|
||||
let currentTimeSeconds = 0;
|
||||
let timerId: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
onMount(() => {
|
||||
timerId = setInterval(() => {
|
||||
currentTimeSeconds = Math.round((Date.now() - sessionStartedAt) / 100) / 10;
|
||||
}, 200);
|
||||
return () => {
|
||||
if (timerId != null) clearInterval(timerId);
|
||||
};
|
||||
});
|
||||
|
||||
$: i18n = locale === "zh-CN"
|
||||
? { now: "当前", min: "最小", max: "最大", waiting: "等待数据" }
|
||||
: { now: "Now", min: "Min", max: "Max", waiting: "Waiting" };
|
||||
|
||||
const viewportWidth = 120;
|
||||
const viewportHeight = 48;
|
||||
@@ -50,7 +70,12 @@
|
||||
}
|
||||
|
||||
if (axis === "x") {
|
||||
return String(Math.round(value));
|
||||
if (value < 60) {
|
||||
return `${value.toFixed(1)}s`;
|
||||
}
|
||||
const mins = Math.floor(value / 60);
|
||||
const secs = value - mins * 60;
|
||||
return `${mins}:${secs.toFixed(0).padStart(2, "0")}`;
|
||||
}
|
||||
|
||||
return `${Math.round(value)} N`;
|
||||
@@ -104,14 +129,51 @@
|
||||
return [];
|
||||
}
|
||||
|
||||
let previousX = 0;
|
||||
|
||||
return rawYValues.map((rawY, index) => {
|
||||
const x = rawXValues[index];
|
||||
const y = Number.isFinite(rawY) ? Number(rawY) : 0;
|
||||
const resolvedX = Number.isFinite(x) ? Number(x) : index + 1;
|
||||
return { x: resolvedX, y };
|
||||
const fallbackX = index === 0 ? 0 : previousX + 1;
|
||||
const resolvedX = Number.isFinite(x) ? Number(x) : fallbackX;
|
||||
const normalizedX = index === 0 ? resolvedX : Math.max(resolvedX, previousX);
|
||||
previousX = normalizedX;
|
||||
return { x: normalizedX, y };
|
||||
});
|
||||
}
|
||||
|
||||
function resolveXScaleBounds(
|
||||
samples: CurveSample[],
|
||||
currentSeconds: number,
|
||||
realtime: boolean
|
||||
): { min: number; max: number } {
|
||||
if (samples.length === 0) {
|
||||
return { min: 0, max: 1 };
|
||||
}
|
||||
|
||||
const values = samples.map((sample) => sample.x);
|
||||
const dataBounds = resolveBounds(values);
|
||||
|
||||
if (!realtime) {
|
||||
return dataBounds;
|
||||
}
|
||||
|
||||
const firstX = samples[0].x;
|
||||
const lastX = samples[samples.length - 1].x;
|
||||
const axisMax = Math.max(lastX, currentSeconds);
|
||||
const positiveDiffs = samples
|
||||
.slice(1)
|
||||
.map((sample, index) => sample.x - samples[index].x)
|
||||
.filter((diff) => diff > 0);
|
||||
const averageSpacing =
|
||||
positiveDiffs.length > 0 ? positiveDiffs.reduce((sum, diff) => sum + diff, 0) / positiveDiffs.length : 1;
|
||||
const dataSpan = Math.max(lastX - firstX, 0);
|
||||
const windowSpan = Math.max(dataSpan, averageSpacing * Math.max(samples.length - 1, 1), 1);
|
||||
const axisMin = Math.max(0, axisMax - windowSpan);
|
||||
|
||||
return resolveBounds([axisMin, axisMax]);
|
||||
}
|
||||
|
||||
function convertPoints(
|
||||
samples: CurveSample[],
|
||||
xBounds: { min: number; max: number },
|
||||
@@ -146,14 +208,14 @@
|
||||
}));
|
||||
}
|
||||
|
||||
function buildXAxisTicks(samples: CurveSample[], xScaleBounds: { min: number; max: number }): AxisTick[] {
|
||||
if (!samples.length) {
|
||||
function buildXAxisTicks(xScaleBounds: { min: number; max: number }): AxisTick[] {
|
||||
if (!Number.isFinite(xScaleBounds.min) || !Number.isFinite(xScaleBounds.max)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const first = samples[0].x;
|
||||
const middle = samples[Math.floor((samples.length - 1) / 2)].x;
|
||||
const last = samples[samples.length - 1].x;
|
||||
const first = xScaleBounds.min;
|
||||
const middle = xScaleBounds.min + (xScaleBounds.max - xScaleBounds.min) / 2;
|
||||
const last = xScaleBounds.max;
|
||||
const tickValues = [first, middle, last];
|
||||
return tickValues.map((value) => ({
|
||||
value,
|
||||
@@ -185,9 +247,16 @@
|
||||
|
||||
$: sourceYValues = yValues && yValues.length ? yValues : summary.points;
|
||||
$: sourceXValues = xValues && xValues.length ? xValues : summary.xValues ?? [];
|
||||
$: samples = buildSamples(sourceYValues, sourceXValues);
|
||||
$: samples = (() => {
|
||||
const base = buildSamples(sourceYValues, sourceXValues);
|
||||
if (isRealtime && base.length > 0 && currentTimeSeconds > 0) {
|
||||
const lastSample = base[base.length - 1];
|
||||
base[base.length - 1] = { ...lastSample, x: Math.max(lastSample.x, currentTimeSeconds) };
|
||||
}
|
||||
return base;
|
||||
})();
|
||||
$: sampleCount = samples.length;
|
||||
$: xScaleBounds = resolveBounds(samples.map((sample) => sample.x));
|
||||
$: xScaleBounds = resolveXScaleBounds(samples, currentTimeSeconds, isRealtime);
|
||||
$: yScaleBounds = fixedYBounds;
|
||||
$: xDataBounds = resolveDataBounds(samples.map((sample) => sample.x));
|
||||
$: yDataBounds = resolveDataBounds(samples.map((sample) => sample.y));
|
||||
@@ -196,7 +265,7 @@
|
||||
$: areaPath = createAreaPath(plotPoints);
|
||||
$: lastPoint = plotPoints.length > 0 ? plotPoints[plotPoints.length - 1] : null;
|
||||
$: yAxisTicks = sampleCount > 0 ? buildYAxisTicks(yScaleBounds, yDataBounds) : [];
|
||||
$: xAxisTicks = sampleCount > 0 ? buildXAxisTicks(samples, xScaleBounds) : [];
|
||||
$: xAxisTicks = sampleCount > 0 ? buildXAxisTicks(xScaleBounds) : [];
|
||||
$: latestValue = formatValue(summary.latest);
|
||||
$: minValue = formatValue(summary.min);
|
||||
$: maxValue = formatValue(summary.max);
|
||||
@@ -270,7 +339,7 @@
|
||||
|
||||
{#if sampleCount === 0}
|
||||
<div class="empty-state">
|
||||
<span>Waiting</span>
|
||||
<span>{i18n.waiting}</span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -278,17 +347,17 @@
|
||||
<footer class="panel-foot">
|
||||
<p class="foot-item">
|
||||
<span class="dot tone-cyan"></span>
|
||||
<span class="metric-text">Now</span>
|
||||
<span class="metric-text">{i18n.now}</span>
|
||||
<span class="value">{latestValue}</span>
|
||||
</p>
|
||||
<p class="foot-item">
|
||||
<span class="dot tone-lime"></span>
|
||||
<span class="metric-text">Min</span>
|
||||
<span class="metric-text">{i18n.min}</span>
|
||||
<span class="value">{minValue}</span>
|
||||
</p>
|
||||
<p class="foot-item">
|
||||
<span class="dot tone-orange"></span>
|
||||
<span class="metric-text">Max</span>
|
||||
<span class="metric-text">{i18n.max}</span>
|
||||
<span class="value">{maxValue}</span>
|
||||
</p>
|
||||
</footer>
|
||||
@@ -300,12 +369,10 @@
|
||||
--enter-ms: 1800ms;
|
||||
--fade-ms: 1000ms;
|
||||
overflow: hidden;
|
||||
inline-size: min(100%, clamp(29rem, 38vw, 37rem));
|
||||
aspect-ratio: 1.42 / 1;
|
||||
min-block-size: 20.5rem;
|
||||
inline-size: min(100%, clamp(34rem, 44vw, 44rem));
|
||||
justify-self: start;
|
||||
display: grid;
|
||||
grid-template-rows: auto auto auto;
|
||||
grid-template-rows: auto 1fr auto;
|
||||
gap: 0.68rem;
|
||||
padding: 0.88rem 0.96rem 1rem;
|
||||
border: 1px solid rgb(var(--hud-border-strong-rgb) / 0.42);
|
||||
@@ -404,6 +471,7 @@
|
||||
.chart-stage {
|
||||
position: relative;
|
||||
block-size: clamp(12rem, 15.5vw, 15rem);
|
||||
min-block-size: 5rem;
|
||||
overflow: hidden;
|
||||
border: 1px solid rgb(var(--hud-border-strong-rgb) / 0.32);
|
||||
border-radius: 0.62rem;
|
||||
@@ -474,25 +542,29 @@
|
||||
|
||||
.panel-foot {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex-wrap: nowrap;
|
||||
gap: 0.8rem;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.foot-item {
|
||||
margin: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.28rem;
|
||||
gap: 0.22rem;
|
||||
color: rgb(var(--hud-text-main-rgb) / 0.9);
|
||||
font-size: 0.76rem;
|
||||
letter-spacing: 0.04em;
|
||||
font-size: 0.68rem;
|
||||
letter-spacing: 0.03em;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.metric-text {
|
||||
color: rgb(var(--hud-text-dim-rgb) / 0.82);
|
||||
text-transform: uppercase;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.dot {
|
||||
@@ -519,16 +591,18 @@
|
||||
|
||||
@media (max-width: 1180px) {
|
||||
.signal-panel {
|
||||
inline-size: min(100%, clamp(24rem, 36vw, 31rem));
|
||||
aspect-ratio: 1.48 / 1;
|
||||
min-block-size: 17rem;
|
||||
inline-size: min(100%, clamp(28rem, 40vw, 38rem));
|
||||
}
|
||||
|
||||
.chart-stage {
|
||||
block-size: clamp(10rem, 13vw, 12rem);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-height: 900px) {
|
||||
.signal-panel {
|
||||
inline-size: min(100%, clamp(24rem, 33vw, 30rem));
|
||||
min-block-size: 16.8rem;
|
||||
inline-size: min(100%, clamp(28rem, 38vw, 36rem));
|
||||
padding: 0.7rem 0.76rem 0.8rem;
|
||||
}
|
||||
|
||||
.chart-stage {
|
||||
@@ -538,46 +612,31 @@
|
||||
|
||||
@media (max-height: 760px) {
|
||||
.signal-panel {
|
||||
inline-size: min(100%, clamp(21rem, 29vw, 26rem));
|
||||
min-block-size: 14.4rem;
|
||||
padding: 0.7rem 0.76rem 0.8rem;
|
||||
}
|
||||
|
||||
.panel-foot {
|
||||
margin-block-start: 0.28rem;
|
||||
inline-size: min(100%, clamp(24rem, 34vw, 30rem));
|
||||
padding: 0.62rem 0.68rem 0.72rem;
|
||||
gap: 0.48rem;
|
||||
}
|
||||
|
||||
.chart-stage {
|
||||
block-size: clamp(8.3rem, 9.6vw, 9.8rem);
|
||||
block-size: clamp(8rem, 9.5vw, 9.8rem);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-height: 680px) {
|
||||
.signal-panel {
|
||||
inline-size: min(100%, clamp(18.5rem, 24vw, 22.5rem));
|
||||
min-block-size: 12.4rem;
|
||||
padding: 0.62rem 0.66rem 0.68rem;
|
||||
}
|
||||
|
||||
.panel-head {
|
||||
margin-block-end: 0.26rem;
|
||||
}
|
||||
|
||||
.panel-foot {
|
||||
margin-block-start: 0.18rem;
|
||||
gap: 0.56rem;
|
||||
inline-size: min(100%, clamp(20rem, 28vw, 26rem));
|
||||
padding: 0.52rem 0.58rem 0.6rem;
|
||||
gap: 0.36rem;
|
||||
}
|
||||
|
||||
.chart-stage {
|
||||
block-size: clamp(7rem, 7.8vw, 8rem);
|
||||
block-size: clamp(6.5rem, 8vw, 7.5rem);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.signal-panel {
|
||||
inline-size: 100%;
|
||||
aspect-ratio: 1.7 / 1;
|
||||
min-block-size: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user