update:优化vip飞行特效

This commit is contained in:
2026-07-27 16:55:56 +08:00
parent e6f11d3684
commit 02f30e88ba
4 changed files with 122 additions and 47 deletions
+65 -23
View File
@@ -22,6 +22,14 @@ const props = defineProps({
type: Number,
default: 0,
},
targetLeft: {
type: Number,
default: 0,
},
targetTop: {
type: Number,
default: 0,
},
hitOffsetPx: {
type: Number,
default: 0,
@@ -40,6 +48,8 @@ let timers = [];
const isActive = computed(() => phase.value !== "idle");
const ARROW_IMPACT_MS = 340;
const COMPLETE_FALLBACK_MS = 980;
// 箭头尖端统一从屏幕中下区域出发,箭身自然延伸到屏幕底部之外。
const SHOT_START_VIEWPORT_Y_RATIO = 0.82;
const safeTargetRadius = computed(() => {
const radius = Number(props.targetRadius);
@@ -49,12 +59,33 @@ const safeTargetRadius = computed(() => {
const safeTargetSize = computed(() => {
const width = Number(props.targetWidth);
const height = Number(props.targetHeight);
const left = Number(props.targetLeft);
const top = Number(props.targetTop);
return {
width: Number.isFinite(width) && width > 0 ? width : 0,
height: Number.isFinite(height) && height > 0 ? height : 0,
left: Number.isFinite(left) ? left : 0,
top: Number.isFinite(top) ? top : 0,
};
});
function getWindowSize() {
try {
const info =
typeof uni.getWindowInfo === "function"
? uni.getWindowInfo()
: uni.getSystemInfoSync();
const width = Number(info?.windowWidth);
const height = Number(info?.windowHeight);
return {
width: Number.isFinite(width) && width > 0 ? width : 0,
height: Number.isFinite(height) && height > 0 ? height : 0,
};
} catch {
return { width: 0, height: 0 };
}
}
function hasShotPoint(shot) {
const x = Number(shot?.x);
const y = Number(shot?.y);
@@ -105,23 +136,35 @@ const hitPercent = computed(() => {
};
});
const arrowAngle = computed(() => {
const flightPath = computed(() => {
const size = safeTargetSize.value;
if (!size.width || !size.height) {
const dx = hitPercent.value.left - 50;
const dy = 114 - hitPercent.value.top;
const fallbackAngle = Math.atan2(dx, dy || 1) * (180 / Math.PI);
return Math.max(-18, Math.min(18, fallbackAngle));
}
const windowSize = getWindowSize();
const startX = size.width * 0.5;
const startY = size.height * 1.14;
const endX = size.width * (hitPercent.value.left / 100) + hitOffset.value.x;
const endY = size.height * (hitPercent.value.top / 100) + hitOffset.value.y;
const hasScreenCoordinates =
size.width > 0 &&
size.height > 0 &&
windowSize.width > 0 &&
windowSize.height > 0;
const startX = hasScreenCoordinates
? windowSize.width * 0.5 - size.left
: size.width * 0.5;
const startY = hasScreenCoordinates
? windowSize.height * SHOT_START_VIEWPORT_Y_RATIO - size.top
: size.height * 1.14;
const endX =
size.width * (hitPercent.value.left / 100) + hitOffset.value.x;
const endY =
size.height * (hitPercent.value.top / 100) + hitOffset.value.y;
const dx = endX - startX;
const dy = startY - endY;
const angle = Math.atan2(dx, dy || 1) * (180 / Math.PI);
return Math.max(-18, Math.min(18, angle));
const dy = endY - startY;
return {
startX,
startY,
translateX: dx,
translateY: dy,
angle: Math.atan2(dx, -dy) * (180 / Math.PI),
};
});
function formatPxOffset(value) {
@@ -148,22 +191,21 @@ function getTargetTranslate(percent) {
const arrowMoveStyle = computed(() => {
const size = safeTargetSize.value;
const path = flightPath.value;
let x = getTargetTranslate(hitPercent.value.left - 50);
let y = getTargetTranslate(hitPercent.value.top - 114);
if (size.width && size.height) {
const startX = size.width * 0.5;
const startY = size.height * 1.14;
const endX = size.width * (hitPercent.value.left / 100) + hitOffset.value.x;
const endY = size.height * (hitPercent.value.top / 100) + hitOffset.value.y;
x = `${endX - startX}px`;
y = `${endY - startY}px`;
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-tx": x,
"--shot-ty": y,
"--shot-angle": `${arrowAngle.value}deg`,
"--shot-angle": `${path.angle}deg`,
};
});
@@ -286,8 +328,8 @@ onBeforeUnmount(() => {
.shot-arrow-track {
position: absolute;
left: 50%;
top: 114%;
left: var(--shot-start-x);
top: var(--shot-start-y);
width: 0;
height: 0;
opacity: 0;
+19 -8
View File
@@ -76,7 +76,7 @@ const shotEffect = ref(null);
const hiddenRedLatestKey = ref("");
const hiddenBlueLatestKey = ref("");
const targetShaking = ref(false);
const targetSize = ref({ width: 0, height: 0 });
const targetRect = ref({ left: 0, top: 0, width: 0, height: 0 });
const shakeTimer = ref(null);
const instance = getCurrentInstance();
const ROUND_TIP_OFFSET_Y = -32;
@@ -178,7 +178,7 @@ function shakeTarget() {
});
}
function updateTargetSize() {
function updateTargetRect() {
nextTick(() => {
const query = instance?.proxy
? uni.createSelectorQuery().in(instance.proxy)
@@ -187,18 +187,27 @@ function updateTargetSize() {
query
.select(".target")
.boundingClientRect((rect) => {
const left = Number(rect?.left);
const top = Number(rect?.top);
const width = Number(rect?.width);
const height = Number(rect?.height);
if (!Number.isFinite(width) || !Number.isFinite(height)) return;
if (
!Number.isFinite(left) ||
!Number.isFinite(top) ||
!Number.isFinite(width) ||
!Number.isFinite(height)
) {
return;
}
if (width <= 0 || height <= 0) return;
targetSize.value = { width, height };
targetRect.value = { left, top, width, height };
})
.exec();
});
}
function handleWindowResize() {
updateTargetSize();
updateTargetRect();
}
function shouldHideRedHit(index) {
@@ -402,7 +411,7 @@ async function onReceiveMessage(message) {
onMounted(() => {
uni.$on("socket-inbox", onReceiveMessage);
updateTargetSize();
updateTargetRect();
if (uni.onWindowResize) uni.onWindowResize(handleWindowResize);
});
@@ -515,8 +524,10 @@ onBeforeUnmount(() => {
:shot="shotEffect && shotEffect.shot"
:playKey="shotEffect ? shotEffect.key : ''"
:targetRadius="safeTargetRadius"
:targetWidth="targetSize.width"
:targetHeight="targetSize.height"
:targetLeft="targetRect.left"
:targetTop="targetRect.top"
:targetWidth="targetRect.width"
:targetHeight="targetRect.height"
:hitOffsetPx="currentHitRadiusPx"
@impact="shakeTarget"
@complete="completeShotEffect"
+19 -8
View File
@@ -80,7 +80,7 @@ const shotEffect = ref(null);
const hiddenRedLatestKey = ref("");
const hiddenBlueLatestKey = ref("");
const targetShaking = ref(false);
const targetSize = ref({ width: 0, height: 0 });
const targetRect = ref({ left: 0, top: 0, width: 0, height: 0 });
const shakeTimer = ref(null);
const instance = getCurrentInstance();
const ROUND_TIP_OFFSET_Y = -32;
@@ -180,7 +180,7 @@ function shakeTarget() {
});
}
function updateTargetSize() {
function updateTargetRect() {
nextTick(() => {
const query = instance?.proxy
? uni.createSelectorQuery().in(instance.proxy)
@@ -189,18 +189,27 @@ function updateTargetSize() {
query
.select(".target")
.boundingClientRect((rect) => {
const left = Number(rect?.left);
const top = Number(rect?.top);
const width = Number(rect?.width);
const height = Number(rect?.height);
if (!Number.isFinite(width) || !Number.isFinite(height)) return;
if (
!Number.isFinite(left) ||
!Number.isFinite(top) ||
!Number.isFinite(width) ||
!Number.isFinite(height)
) {
return;
}
if (width <= 0 || height <= 0) return;
targetSize.value = { width, height };
targetRect.value = { left, top, width, height };
})
.exec();
});
}
function handleWindowResize() {
updateTargetSize();
updateTargetRect();
}
function shouldHideRedHit(index) {
@@ -408,7 +417,7 @@ async function onReceiveMessage(message) {
onMounted(() => {
uni.$on("socket-inbox", onReceiveMessage);
updateTargetSize();
updateTargetRect();
if (uni.onWindowResize) uni.onWindowResize(handleWindowResize);
});
@@ -533,8 +542,10 @@ onBeforeUnmount(() => {
:shot="shotEffect && shotEffect.shot"
:playKey="shotEffect ? shotEffect.key : ''"
:targetRadius="safeTargetRadius"
:targetWidth="targetSize.width"
:targetHeight="targetSize.height"
:targetLeft="targetRect.left"
:targetTop="targetRect.top"
:targetWidth="targetRect.width"
:targetHeight="targetRect.height"
:hitOffsetPx="currentHitRadiusPx"
@impact="shakeTarget"
@complete="completeShotEffect"
+19 -8
View File
@@ -100,7 +100,7 @@ const circleColor = ref("");
const shotEffect = ref(null);
const hiddenLatestKey = ref("");
const targetShaking = ref(false);
const targetSize = ref({ width: 0, height: 0 });
const targetRect = ref({ left: 0, top: 0, width: 0, height: 0 });
const shakeTimer = ref(null);
const instance = getCurrentInstance();
const ROUND_TIP_OFFSET_Y = -32;
@@ -291,7 +291,7 @@ function shakeTarget() {
});
}
function updateTargetSize() {
function updateTargetRect() {
nextTick(() => {
const query = instance?.proxy
? uni.createSelectorQuery().in(instance.proxy)
@@ -300,18 +300,27 @@ function updateTargetSize() {
query
.select(".target")
.boundingClientRect((rect) => {
const left = Number(rect?.left);
const top = Number(rect?.top);
const width = Number(rect?.width);
const height = Number(rect?.height);
if (!Number.isFinite(width) || !Number.isFinite(height)) return;
if (
!Number.isFinite(left) ||
!Number.isFinite(top) ||
!Number.isFinite(width) ||
!Number.isFinite(height)
) {
return;
}
if (width <= 0 || height <= 0) return;
targetSize.value = { width, height };
targetRect.value = { left, top, width, height };
})
.exec();
});
}
function handleWindowResize() {
updateTargetSize();
updateTargetRect();
}
watch(
@@ -422,7 +431,7 @@ async function onReceiveMessage(message) {
onMounted(() => {
uni.$on("socket-inbox", onReceiveMessage);
updateTargetSize();
updateTargetRect();
if (uni.onWindowResize) uni.onWindowResize(handleWindowResize);
});
@@ -549,8 +558,10 @@ onBeforeUnmount(() => {
:shot="shotEffect && shotEffect.shot"
:playKey="shotEffect ? shotEffect.key : ''"
:targetRadius="safeTargetRadius"
:targetWidth="targetSize.width"
:targetHeight="targetSize.height"
:targetLeft="targetRect.left"
:targetTop="targetRect.top"
:targetWidth="targetRect.width"
:targetHeight="targetRect.height"
:hitOffsetPx="currentHitRadiusPx"
@impact="shakeTarget"
@complete="completeShotEffect"