diff --git a/src/components/BowTarget.vue b/src/components/BowTarget.vue index b1e6c5c..79ee41d 100644 --- a/src/components/BowTarget.vue +++ b/src/components/BowTarget.vue @@ -34,6 +34,18 @@ const props = defineProps({ type: Boolean, default: false, }, + targetRadius: { + type: Number, + default: 20, + }, + hitRadiusPx: { + type: Number, + default: 2, + }, + zoomHitRadiusPx: { + type: Number, + default: 5, + }, }); const pMode = ref(true); @@ -80,13 +92,79 @@ watch( } ); -function calcRealX(num, offset = 3.4) { - const len = 20.4 + num; - return `calc(${(len / 40.8) * 100 - offset / 2}%)`; +const safeTargetRadius = computed(() => { + const radius = Number(props.targetRadius); + return Number.isFinite(radius) && radius > 0 ? radius : 20; +}); + +const currentHitRadiusPx = computed(() => { + const radius = Number( + pMode.value ? props.zoomHitRadiusPx : props.hitRadiusPx + ); + return Number.isFinite(radius) && radius >= 0 ? radius : 0; +}); + +function getShotPoint(shot, fallbackCenter = false) { + const x = Number(shot?.x); + const y = Number(shot?.y); + if (Number.isFinite(x) && Number.isFinite(y)) return { x, y }; + return fallbackCenter ? { x: 0, y: 0 } : null; } -function calcRealY(num, offset = 3.4) { - const len = num < 0 ? Math.abs(num) + 20.4 : 20.4 - num; - return `calc(${(len / 40.8) * 100 - offset / 2}%)`; + +function getPointDirection(point) { + if (!point) return null; + const distance = Math.sqrt(point.x * point.x + point.y * point.y); + if (distance === 0) return null; + + return { + x: point.x / distance, + y: point.y / distance, + }; +} + +function formatPxOffset(value) { + if (!value) return ""; + const operator = value > 0 ? "+" : "-"; + return ` ${operator} ${Math.abs(value)}px`; +} + +function formatTargetPosition(percent, offset) { + const pxOffset = formatPxOffset(offset); + return pxOffset ? `calc(${percent}%${pxOffset})` : `${percent}%`; +} + +function getTargetPositionStyle(point, offsetPx = 0) { + if (!point) return { display: "none" }; + + const radius = safeTargetRadius.value; + const diameter = radius * 2; + const direction = getPointDirection(point); + const xOffset = direction ? direction.x * offsetPx : 0; + const yOffset = direction ? -direction.y * offsetPx : 0; + const leftPercent = ((point.x + radius) / diameter) * 100; + const topPercent = ((radius - point.y) / diameter) * 100; + + return { + left: formatTargetPosition(leftPercent, xOffset), + top: formatTargetPosition(topPercent, yOffset), + transform: "translate(-50%, -50%)", + }; +} + +function getHitStyle(shot) { + const radius = currentHitRadiusPx.value; + const point = getShotPoint(shot); + + return { + ...getTargetPositionStyle(point, radius), + width: `${radius * 2}px`, + height: `${radius * 2}px`, + }; +} + +function getTipStyle(shot) { + const point = getShotPoint(shot, true); + return getTargetPositionStyle(point, shot?.ring ? currentHitRadiusPx.value : 0); } const simulShoot = async () => { if (device.value.deviceId) await simulShootAPI(device.value.deviceId); @@ -169,20 +247,14 @@ onBeforeUnmount(() => { 经验 +1 {{ latestOne.ringX ? "X" : latestOne.ring || "未上靶" }} @@ -193,20 +265,14 @@ onBeforeUnmount(() => { user.id === bluelatestOne.playerId " class="e-value fade-in-out" - :style="{ - left: calcRealX(bluelatestOne.ring ? bluelatestOne.x : 0, 20), - top: calcRealY(bluelatestOne.ring ? bluelatestOne.y : 0, 40), - }" + :style="getTipStyle(bluelatestOne)" > 经验 +1 {{ bluelatestOne.ringX ? "X" : bluelatestOne.ring || "未上靶" }} @@ -217,8 +283,7 @@ onBeforeUnmount(() => { index === scores.length - 1 && latestOne ? 'pump-in' : '' }`" :style="{ - left: calcRealX(bow.x, pMode ? '3.4' : '2'), - top: calcRealY(bow.y, pMode ? '3.4' : '2'), + ...getHitStyle(bow), backgroundColor: mode === 'solo' ? '#00bf04' : '#FF0000', }" >{{ index + 1 }} { index === blueScores.length - 1 && bluelatestOne ? 'pump-in' : '' }`" :style="{ - left: calcRealX(bow.x, pMode ? '3.4' : '2'), - top: calcRealY(bow.y, pMode ? '3.4' : '2'), + ...getHitStyle(bow), backgroundColor: '#1840FF', }" > @@ -302,21 +366,11 @@ onBeforeUnmount(() => { z-index: 1; color: #fff; transition: all 0.3s ease; -} -.s-point { - width: 4px; - height: 4px; - min-width: 4px; - min-height: 4px; + box-sizing: border-box; } .b-point { - width: 10px; - height: 10px; - min-width: 10px; - min-height: 10px; border: 1px solid #fff; z-index: 1; - box-sizing: border-box; display: flex; justify-content: center; align-items: center; @@ -332,6 +386,19 @@ onBeforeUnmount(() => { transform: translate(-50%, -50%);*/ margin-top: 2rpx; } +@keyframes target-pump-in { + from { + transform: translate(-50%, -50%) scale(2); + } + + to { + transform: translate(-50%, -50%) scale(1); + } +} +.hit.pump-in { + animation: target-pump-in 0.3s ease-out forwards; + transform-origin: center center; +} .header { width: 100%; display: flex; diff --git a/src/pages/team-battle/components/BowTarget.vue b/src/pages/team-battle/components/BowTarget.vue index 18f82cc..c855fea 100644 --- a/src/pages/team-battle/components/BowTarget.vue +++ b/src/pages/team-battle/components/BowTarget.vue @@ -38,6 +38,18 @@ const props = defineProps({ type: Boolean, default: false, }, + targetRadius: { + type: Number, + default: 20, + }, + hitRadiusPx: { + type: Number, + default: 2, + }, + zoomHitRadiusPx: { + type: Number, + default: 5, + }, }); const pMode = ref(true); @@ -75,13 +87,79 @@ watch( { immediate: true } ); -function calcRealX(num, offset = 3.4) { - const len = 20.4 + num; - return `calc(${(len / 40.8) * 100 - offset / 2}%)`; +const safeTargetRadius = computed(() => { + const radius = Number(props.targetRadius); + return Number.isFinite(radius) && radius > 0 ? radius : 20; +}); + +const currentHitRadiusPx = computed(() => { + const radius = Number( + pMode.value ? props.zoomHitRadiusPx : props.hitRadiusPx + ); + return Number.isFinite(radius) && radius >= 0 ? radius : 0; +}); + +function getShotPoint(shot, fallbackCenter = false) { + const x = Number(shot?.x); + const y = Number(shot?.y); + if (Number.isFinite(x) && Number.isFinite(y)) return { x, y }; + return fallbackCenter ? { x: 0, y: 0 } : null; } -function calcRealY(num, offset = 3.4) { - const len = num < 0 ? Math.abs(num) + 20.4 : 20.4 - num; - return `calc(${(len / 40.8) * 100 - offset / 2}%)`; + +function getPointDirection(point) { + if (!point) return null; + const distance = Math.sqrt(point.x * point.x + point.y * point.y); + if (distance === 0) return null; + + return { + x: point.x / distance, + y: point.y / distance, + }; +} + +function formatPxOffset(value) { + if (!value) return ""; + const operator = value > 0 ? "+" : "-"; + return ` ${operator} ${Math.abs(value)}px`; +} + +function formatTargetPosition(percent, offset) { + const pxOffset = formatPxOffset(offset); + return pxOffset ? `calc(${percent}%${pxOffset})` : `${percent}%`; +} + +function getTargetPositionStyle(point, offsetPx = 0) { + if (!point) return { display: "none" }; + + const radius = safeTargetRadius.value; + const diameter = radius * 2; + const direction = getPointDirection(point); + const xOffset = direction ? direction.x * offsetPx : 0; + const yOffset = direction ? -direction.y * offsetPx : 0; + const leftPercent = ((point.x + radius) / diameter) * 100; + const topPercent = ((radius - point.y) / diameter) * 100; + + return { + left: formatTargetPosition(leftPercent, xOffset), + top: formatTargetPosition(topPercent, yOffset), + transform: "translate(-50%, -50%)", + }; +} + +function getHitStyle(shot) { + const radius = currentHitRadiusPx.value; + const point = getShotPoint(shot); + + return { + ...getTargetPositionStyle(point, radius), + width: `${radius * 2}px`, + height: `${radius * 2}px`, + }; +} + +function getTipStyle(shot) { + const point = getShotPoint(shot, true); + return getTargetPositionStyle(point, shot?.ring ? currentHitRadiusPx.value : 0); } const simulShoot = async () => { if (device.value.deviceId) await simulShootAPI(device.value.deviceId); @@ -164,20 +242,14 @@ onBeforeUnmount(() => { 经验 +1 {{ latestOne.ringX ? "X" : latestOne.ring || "未上靶" }} @@ -188,20 +260,14 @@ onBeforeUnmount(() => { user.id === bluelatestOne.playerId " class="e-value fade-in-out" - :style="{ - left: calcRealX(bluelatestOne.ring ? bluelatestOne.x : 0, 20), - top: calcRealY(bluelatestOne.ring ? bluelatestOne.y : 0, 40), - }" + :style="getTipStyle(bluelatestOne)" > 经验 +1 {{ bluelatestOne.ringX ? "X" : bluelatestOne.ring || "未上靶" }} @@ -212,8 +278,7 @@ onBeforeUnmount(() => { index === scores.length - 1 && latestOne ? 'pump-in' : '' }`" :style="{ - left: calcRealX(bow.x, pMode ? '3.4' : '2'), - top: calcRealY(bow.y, pMode ? '3.4' : '2'), + ...getHitStyle(bow), backgroundColor: mode === 'solo' ? '#00bf04' : '#FF0000', }" >{{ index + 1 }} { index === blueScores.length - 1 && bluelatestOne ? 'pump-in' : '' }`" :style="{ - left: calcRealX(bow.x, pMode ? '3.4' : '2'), - top: calcRealY(bow.y, pMode ? '3.4' : '2'), + ...getHitStyle(bow), backgroundColor: '#1840FF', }" > @@ -297,21 +361,11 @@ onBeforeUnmount(() => { z-index: 1; color: #fff; transition: all 0.3s ease; -} -.s-point { - width: 4px; - height: 4px; - min-width: 4px; - min-height: 4px; + box-sizing: border-box; } .b-point { - width: 10px; - height: 10px; - min-width: 10px; - min-height: 10px; border: 1px solid #fff; z-index: 1; - box-sizing: border-box; display: flex; justify-content: center; align-items: center; @@ -327,6 +381,19 @@ onBeforeUnmount(() => { transform: translate(-50%, -50%);*/ margin-top: 2rpx; } +@keyframes target-pump-in { + from { + transform: translate(-50%, -50%) scale(2); + } + + to { + transform: translate(-50%, -50%) scale(1); + } +} +.hit.pump-in { + animation: target-pump-in 0.3s ease-out forwards; + transform-origin: center center; +} .header { width: 100%; display: flex;