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