update:优化vip飞行特效
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user