update:优化稳定训练
This commit is contained in:
@@ -18,13 +18,15 @@ const props = defineProps({
|
|||||||
|
|
||||||
const WIDTH = 750;
|
const WIDTH = 750;
|
||||||
const HEIGHT = 228;
|
const HEIGHT = 228;
|
||||||
const MASK_SCALE = 3;
|
const MASK_SCALE = 2;
|
||||||
const MASK_WIDTH = WIDTH / MASK_SCALE;
|
const MASK_WIDTH = WIDTH / MASK_SCALE;
|
||||||
const MASK_HEIGHT = HEIGHT / MASK_SCALE;
|
const MASK_HEIGHT = HEIGHT / MASK_SCALE;
|
||||||
const ENTRY_PADDING = 24;
|
const ENTRY_PADDING = 24;
|
||||||
const EXIT_PADDING = 32;
|
const EXIT_PADDING = 32;
|
||||||
const MAX_FEATHER = 60;
|
const MAX_FEATHER = 60;
|
||||||
const FULL_TRACK_OFFSET_Y = -3;
|
const FULL_TRACK_OFFSET_Y = -3;
|
||||||
|
const ENTRY_EDGE_BLEND_PERCENT = 5;
|
||||||
|
const EXIT_EDGE_BLEND_START_PERCENT = 90;
|
||||||
const SWEEP_TRACK_RADIUS = 28;
|
const SWEEP_TRACK_RADIUS = 28;
|
||||||
const SWEEP_TRACK_FEATHER = 8;
|
const SWEEP_TRACK_FEATHER = 8;
|
||||||
const ENTRY_ANIMATION_DURATION = 1000;
|
const ENTRY_ANIMATION_DURATION = 1000;
|
||||||
@@ -32,6 +34,9 @@ const UPDATE_DURATION = 240;
|
|||||||
const SWEEP_DELAY = 100;
|
const SWEEP_DELAY = 100;
|
||||||
const SWEEP_DURATION = 1000;
|
const SWEEP_DURATION = 1000;
|
||||||
const ASSET_ROOT = "https://static.shelingxingqiu.com/shootmini/static/training-difficulty-design/stability-energy";
|
const ASSET_ROOT = "https://static.shelingxingqiu.com/shootmini/static/training-difficulty-design/stability-energy";
|
||||||
|
const ASSET_VERSION = "20260922";
|
||||||
|
const getAssetSource = (filename) =>
|
||||||
|
`${ASSET_ROOT}/${filename}?v=${ASSET_VERSION}`;
|
||||||
|
|
||||||
const instance = getCurrentInstance();
|
const instance = getCurrentInstance();
|
||||||
const canvasId = `stability-energy-${Math.random().toString(36).slice(2, 10)}`;
|
const canvasId = `stability-energy-${Math.random().toString(36).slice(2, 10)}`;
|
||||||
@@ -42,9 +47,9 @@ const normalizedPercent = computed(() =>
|
|||||||
const shouldPlayEntryAnimation = ref(normalizedPercent.value <= 0);
|
const shouldPlayEntryAnimation = ref(normalizedPercent.value <= 0);
|
||||||
const fallbackImageSource = computed(
|
const fallbackImageSource = computed(
|
||||||
() =>
|
() =>
|
||||||
`${ASSET_ROOT}/${
|
getAssetSource(
|
||||||
shouldPlayEntryAnimation.value ? "progress-full.png" : "progress-empty.png"
|
shouldPlayEntryAnimation.value ? "progress-full.png" : "progress-empty.png"
|
||||||
}`
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
let engine = null;
|
let engine = null;
|
||||||
@@ -81,15 +86,25 @@ const smoothStep = (value) => {
|
|||||||
return amount * amount * (3 - 2 * amount);
|
return amount * amount * (3 - 2 * amount);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const enableImageSmoothing = (context) => {
|
||||||
|
if ("imageSmoothingEnabled" in context) {
|
||||||
|
context.imageSmoothingEnabled = true;
|
||||||
|
}
|
||||||
|
if ("imageSmoothingQuality" in context) {
|
||||||
|
context.imageSmoothingQuality = "high";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 复用参考 HTML 的贝塞尔路径,保证能量光带沿靶纸下方的弧线推进。
|
// 复用参考 HTML 的贝塞尔路径,保证能量光带沿靶纸下方的弧线推进。
|
||||||
const sampleTrack = () => {
|
const sampleTrack = () => {
|
||||||
const curves = [
|
const curves = [
|
||||||
[280, 26, 226, 31, 121, 49, 125, 82],
|
// 新素材移除了左上方延伸段,进度从箭头端点开始进入弯道。
|
||||||
|
[171, 53, 145, 56, 123, 68, 125, 82],
|
||||||
[125, 82, 128, 128, 272, 163, 412, 163],
|
[125, 82, 128, 128, 272, 163, 412, 163],
|
||||||
[412, 163, 530, 164, 650, 137, 657, 98],
|
[412, 163, 530, 164, 650, 137, 657, 98],
|
||||||
[657, 98, 659, 86, 650, 76, 643, 72],
|
[657, 98, 659, 86, 650, 76, 643, 72],
|
||||||
];
|
];
|
||||||
const points = [{ x: 280, y: 26, distance: 0 }];
|
const points = [{ x: 171, y: 53, distance: 0 }];
|
||||||
|
|
||||||
curves.forEach((curve) => {
|
curves.forEach((curve) => {
|
||||||
for (let step = 1; step <= 32; step += 1) {
|
for (let step = 1; step <= 32; step += 1) {
|
||||||
@@ -149,6 +164,7 @@ const createOffscreenCanvas = (canvas, width = WIDTH, height = HEIGHT) => {
|
|||||||
layerCanvas.height = height;
|
layerCanvas.height = height;
|
||||||
const context = layerCanvas.getContext("2d");
|
const context = layerCanvas.getContext("2d");
|
||||||
if (!context) throw new Error("Offscreen canvas context unavailable");
|
if (!context) throw new Error("Offscreen canvas context unavailable");
|
||||||
|
enableImageSmoothing(context);
|
||||||
return { canvas: layerCanvas, context };
|
return { canvas: layerCanvas, context };
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -172,6 +188,7 @@ const loadCanvasImage = (canvas, source) =>
|
|||||||
const createEnergyEngine = async (canvas) => {
|
const createEnergyEngine = async (canvas) => {
|
||||||
const context = canvas.getContext("2d");
|
const context = canvas.getContext("2d");
|
||||||
if (!context) throw new Error("Canvas unavailable");
|
if (!context) throw new Error("Canvas unavailable");
|
||||||
|
enableImageSmoothing(context);
|
||||||
|
|
||||||
canvas.width = WIDTH;
|
canvas.width = WIDTH;
|
||||||
canvas.height = HEIGHT;
|
canvas.height = HEIGHT;
|
||||||
@@ -190,9 +207,9 @@ const createEnergyEngine = async (canvas) => {
|
|||||||
maskPixels.data.fill(255);
|
maskPixels.data.fill(255);
|
||||||
|
|
||||||
const images = await Promise.all([
|
const images = await Promise.all([
|
||||||
loadCanvasImage(canvas, `${ASSET_ROOT}/progress-empty.png`),
|
loadCanvasImage(canvas, getAssetSource("progress-empty.png")),
|
||||||
loadCanvasImage(canvas, `${ASSET_ROOT}/progress-full.png`),
|
loadCanvasImage(canvas, getAssetSource("progress-full.png")),
|
||||||
loadCanvasImage(canvas, `${ASSET_ROOT}/progress-glow.png`),
|
loadCanvasImage(canvas, getAssetSource("progress-glow.png")),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const segments = track.slice(1).map((end, index) => {
|
const segments = track.slice(1).map((end, index) => {
|
||||||
@@ -338,11 +355,20 @@ const createEnergyEngine = async (canvas) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 真实百分比只映射轨道本体,避免 90%~100% 被出口和羽化缓冲吞掉。
|
// 主体百分比仍映射轨道本体,头尾仅补偿端点羽化,避免整图与蒙版切换时跳变。
|
||||||
const distance = (progress / 100) * pathLength;
|
const distance = (progress / 100) * pathLength;
|
||||||
|
const entryBlend = smoothStep(progress / ENTRY_EDGE_BLEND_PERCENT);
|
||||||
|
const exitBlend = smoothStep(
|
||||||
|
(progress - EXIT_EDGE_BLEND_START_PERCENT) /
|
||||||
|
(100 - EXIT_EDGE_BLEND_START_PERCENT)
|
||||||
|
);
|
||||||
for (let pixel = 0; pixel < revealStarts.length; pixel += 1) {
|
for (let pixel = 0; pixel < revealStarts.length; pixel += 1) {
|
||||||
|
const trackAlpha =
|
||||||
|
smoothStep(
|
||||||
|
(distance - revealStarts[pixel]) * revealRates[pixel]
|
||||||
|
) * entryBlend;
|
||||||
maskPixels.data[pixel * 4 + 3] = Math.round(
|
maskPixels.data[pixel * 4 + 3] = Math.round(
|
||||||
255 * smoothStep((distance - revealStarts[pixel]) * revealRates[pixel])
|
255 * (trackAlpha + (1 - trackAlpha) * exitBlend)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
maskLayer.context.putImageData(maskPixels, 0, 0);
|
maskLayer.context.putImageData(maskPixels, 0, 0);
|
||||||
@@ -361,12 +387,14 @@ const createEnergyEngine = async (canvas) => {
|
|||||||
const headBlend =
|
const headBlend =
|
||||||
smoothStep(progress / 10) * (1 - smoothStep((progress - 85) / 15));
|
smoothStep(progress / 10) * (1 - smoothStep((progress - 85) / 15));
|
||||||
const position = trackPosition(distance - 16);
|
const position = trackPosition(distance - 16);
|
||||||
const headSize = 72 * (0.55 + 0.45 * headBlend);
|
const headSize = Math.round(72 * (0.55 + 0.45 * headBlend));
|
||||||
|
const headX = Math.round(position.x - headSize / 2);
|
||||||
|
const headY = Math.round(position.y - headSize / 2);
|
||||||
glowLayer.context.clearRect(0, 0, WIDTH, HEIGHT);
|
glowLayer.context.clearRect(0, 0, WIDTH, HEIGHT);
|
||||||
glowLayer.context.drawImage(
|
glowLayer.context.drawImage(
|
||||||
images[2],
|
images[2],
|
||||||
position.x - headSize / 2,
|
headX,
|
||||||
position.y - headSize / 2,
|
headY,
|
||||||
headSize,
|
headSize,
|
||||||
headSize
|
headSize
|
||||||
);
|
);
|
||||||
@@ -621,6 +649,6 @@ onBeforeUnmount(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.stability-energy-track__image--full {
|
.stability-energy-track__image--full {
|
||||||
transform: translateY(-3rpx);
|
transform: translateY(-2.8rpx);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 34 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 47 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 8.4 KiB |
Reference in New Issue
Block a user