update:优化稳定训练
This commit is contained in:
@@ -18,13 +18,15 @@ const props = defineProps({
|
||||
|
||||
const WIDTH = 750;
|
||||
const HEIGHT = 228;
|
||||
const MASK_SCALE = 3;
|
||||
const MASK_SCALE = 2;
|
||||
const MASK_WIDTH = WIDTH / MASK_SCALE;
|
||||
const MASK_HEIGHT = HEIGHT / MASK_SCALE;
|
||||
const ENTRY_PADDING = 24;
|
||||
const EXIT_PADDING = 32;
|
||||
const MAX_FEATHER = 60;
|
||||
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_FEATHER = 8;
|
||||
const ENTRY_ANIMATION_DURATION = 1000;
|
||||
@@ -32,6 +34,9 @@ const UPDATE_DURATION = 240;
|
||||
const SWEEP_DELAY = 100;
|
||||
const SWEEP_DURATION = 1000;
|
||||
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 canvasId = `stability-energy-${Math.random().toString(36).slice(2, 10)}`;
|
||||
@@ -42,9 +47,9 @@ const normalizedPercent = computed(() =>
|
||||
const shouldPlayEntryAnimation = ref(normalizedPercent.value <= 0);
|
||||
const fallbackImageSource = computed(
|
||||
() =>
|
||||
`${ASSET_ROOT}/${
|
||||
getAssetSource(
|
||||
shouldPlayEntryAnimation.value ? "progress-full.png" : "progress-empty.png"
|
||||
}`
|
||||
)
|
||||
);
|
||||
|
||||
let engine = null;
|
||||
@@ -81,15 +86,25 @@ const smoothStep = (value) => {
|
||||
return amount * amount * (3 - 2 * amount);
|
||||
};
|
||||
|
||||
const enableImageSmoothing = (context) => {
|
||||
if ("imageSmoothingEnabled" in context) {
|
||||
context.imageSmoothingEnabled = true;
|
||||
}
|
||||
if ("imageSmoothingQuality" in context) {
|
||||
context.imageSmoothingQuality = "high";
|
||||
}
|
||||
};
|
||||
|
||||
// 复用参考 HTML 的贝塞尔路径,保证能量光带沿靶纸下方的弧线推进。
|
||||
const sampleTrack = () => {
|
||||
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],
|
||||
[412, 163, 530, 164, 650, 137, 657, 98],
|
||||
[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) => {
|
||||
for (let step = 1; step <= 32; step += 1) {
|
||||
@@ -149,6 +164,7 @@ const createOffscreenCanvas = (canvas, width = WIDTH, height = HEIGHT) => {
|
||||
layerCanvas.height = height;
|
||||
const context = layerCanvas.getContext("2d");
|
||||
if (!context) throw new Error("Offscreen canvas context unavailable");
|
||||
enableImageSmoothing(context);
|
||||
return { canvas: layerCanvas, context };
|
||||
};
|
||||
|
||||
@@ -172,6 +188,7 @@ const loadCanvasImage = (canvas, source) =>
|
||||
const createEnergyEngine = async (canvas) => {
|
||||
const context = canvas.getContext("2d");
|
||||
if (!context) throw new Error("Canvas unavailable");
|
||||
enableImageSmoothing(context);
|
||||
|
||||
canvas.width = WIDTH;
|
||||
canvas.height = HEIGHT;
|
||||
@@ -190,9 +207,9 @@ const createEnergyEngine = async (canvas) => {
|
||||
maskPixels.data.fill(255);
|
||||
|
||||
const images = await Promise.all([
|
||||
loadCanvasImage(canvas, `${ASSET_ROOT}/progress-empty.png`),
|
||||
loadCanvasImage(canvas, `${ASSET_ROOT}/progress-full.png`),
|
||||
loadCanvasImage(canvas, `${ASSET_ROOT}/progress-glow.png`),
|
||||
loadCanvasImage(canvas, getAssetSource("progress-empty.png")),
|
||||
loadCanvasImage(canvas, getAssetSource("progress-full.png")),
|
||||
loadCanvasImage(canvas, getAssetSource("progress-glow.png")),
|
||||
]);
|
||||
|
||||
const segments = track.slice(1).map((end, index) => {
|
||||
@@ -338,11 +355,20 @@ const createEnergyEngine = async (canvas) => {
|
||||
return;
|
||||
}
|
||||
|
||||
// 真实百分比只映射轨道本体,避免 90%~100% 被出口和羽化缓冲吞掉。
|
||||
// 主体百分比仍映射轨道本体,头尾仅补偿端点羽化,避免整图与蒙版切换时跳变。
|
||||
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) {
|
||||
const trackAlpha =
|
||||
smoothStep(
|
||||
(distance - revealStarts[pixel]) * revealRates[pixel]
|
||||
) * entryBlend;
|
||||
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);
|
||||
@@ -361,12 +387,14 @@ const createEnergyEngine = async (canvas) => {
|
||||
const headBlend =
|
||||
smoothStep(progress / 10) * (1 - smoothStep((progress - 85) / 15));
|
||||
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.drawImage(
|
||||
images[2],
|
||||
position.x - headSize / 2,
|
||||
position.y - headSize / 2,
|
||||
headX,
|
||||
headY,
|
||||
headSize,
|
||||
headSize
|
||||
);
|
||||
@@ -621,6 +649,6 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
.stability-energy-track__image--full {
|
||||
transform: translateY(-3rpx);
|
||||
transform: translateY(-2.8rpx);
|
||||
}
|
||||
</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