diff --git a/src/components/BowShotEffect.vue b/src/components/BowShotEffect.vue index 058f5d5..171c9a3 100644 --- a/src/components/BowShotEffect.vue +++ b/src/components/BowShotEffect.vue @@ -34,6 +34,10 @@ const props = defineProps({ type: Number, default: 0, }, + viewportMode: { + type: Boolean, + default: false, + }, }); const emit = defineEmits(["complete", "impact"]); @@ -43,6 +47,7 @@ const activePlayKey = ref(""); const animationKey = ref(""); const impactEmitted = ref(false); const activeShot = ref(null); +const activeLayout = ref(null); let timers = []; const isActive = computed(() => phase.value !== "idle"); @@ -93,6 +98,17 @@ function hasShotPoint(shot) { } const effectiveShot = computed(() => activeShot.value || props.shot); +const effectiveTargetSize = computed( + () => activeLayout.value?.target || safeTargetSize.value +); +const effectiveWindowSize = computed( + () => activeLayout.value?.window || getWindowSize() +); +const isViewportMode = computed(() => + activeLayout.value + ? activeLayout.value.viewportMode + : props.viewportMode === true +); const shotPoint = computed(() => { const x = Number(effectiveShot.value?.x); @@ -137,8 +153,40 @@ const hitPercent = computed(() => { }); const flightPath = computed(() => { - const size = safeTargetSize.value; - const windowSize = getWindowSize(); + const size = effectiveTargetSize.value; + const windowSize = effectiveWindowSize.value; + + if ( + isViewportMode.value && + size.width > 0 && + size.height > 0 && + windowSize.width > 0 && + windowSize.height > 0 + ) { + const startX = windowSize.width * 0.5; + const endX = + size.left + + size.width * (hitPercent.value.left / 100) + + hitOffset.value.x; + const endY = + size.top + + size.height * (hitPercent.value.top / 100) + + hitOffset.value.y; + // 常规情况下从视口底部进入;靶面局部超出视口时仍保证起点在命中点下方。 + const startY = Math.max(windowSize.height + 16, endY + 80); + const dx = endX - startX; + const dy = endY - startY; + + return { + startX, + startY, + endX, + endY, + translateX: dx, + translateY: dy, + angle: Math.atan2(dx, -dy) * (180 / Math.PI), + }; + } const hasScreenCoordinates = size.width > 0 && @@ -161,6 +209,8 @@ const flightPath = computed(() => { return { startX, startY, + endX, + endY, translateX: dx, translateY: dy, angle: Math.atan2(dx, -dy) * (180 / Math.PI), @@ -178,10 +228,19 @@ function formatTargetPosition(percent, offset) { return pxOffset ? `calc(${percent}%${pxOffset})` : `${percent}%`; } -const crackStyle = computed(() => ({ - left: formatTargetPosition(hitPercent.value.left, hitOffset.value.x), - top: formatTargetPosition(hitPercent.value.top, hitOffset.value.y), -})); +const crackStyle = computed(() => { + if (isViewportMode.value) { + return { + left: `${flightPath.value.endX}px`, + top: `${flightPath.value.endY}px`, + }; + } + + return { + left: formatTargetPosition(hitPercent.value.left, hitOffset.value.x), + top: formatTargetPosition(hitPercent.value.top, hitOffset.value.y), + }; +}); function getTargetTranslate(percent) { const absPercent = Math.abs(percent); @@ -190,19 +249,21 @@ function getTargetTranslate(percent) { } const arrowMoveStyle = computed(() => { - const size = safeTargetSize.value; + const size = effectiveTargetSize.value; const path = flightPath.value; let x = getTargetTranslate(hitPercent.value.left - 50); let y = getTargetTranslate(hitPercent.value.top - 114); - if (size.width && size.height) { + if (isViewportMode.value || (size.width && size.height)) { x = `${path.translateX}px`; y = `${path.translateY}px`; } return { - "--shot-start-x": size.width ? `${path.startX}px` : "50%", - "--shot-start-y": size.height ? `${path.startY}px` : "114%", + "--shot-start-x": + isViewportMode.value || size.width ? `${path.startX}px` : "50%", + "--shot-start-y": + isViewportMode.value || size.height ? `${path.startY}px` : "114%", "--shot-tx": x, "--shot-ty": y, "--shot-angle": `${path.angle}deg`, @@ -231,6 +292,7 @@ function finish(playKey) { phase.value = "idle"; activePlayKey.value = ""; activeShot.value = null; + activeLayout.value = null; emit("complete", playKey); } @@ -244,6 +306,11 @@ function play() { animationKey.value = `${props.playKey}`; impactEmitted.value = false; activeShot.value = { ...props.shot }; + activeLayout.value = { + target: { ...safeTargetSize.value }, + window: getWindowSize(), + viewportMode: props.viewportMode === true, + }; phase.value = "playing"; queueTimer(() => { @@ -278,7 +345,11 @@ onBeforeUnmount(() => {