update: 修复飞行特效

This commit is contained in:
2026-08-04 16:18:30 +08:00
parent ab70c8c78e
commit 3c45ef78ac
8 changed files with 406 additions and 104 deletions
+86 -9
View File
@@ -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(() => ({
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(() => {
<template>
<view
v-show="isActive"
:class="['shot-effect', `shot-effect--${phase}`]"
:class="[
'shot-effect',
`shot-effect--${phase}`,
{ 'shot-effect--viewport': isViewportMode },
]"
:style="arrowMoveStyle"
>
<view
@@ -326,6 +397,12 @@ onBeforeUnmount(() => {
transform: translateZ(0);
}
.shot-effect--viewport {
position: fixed;
width: 100vw;
height: 100vh;
}
.shot-arrow-track {
position: absolute;
left: var(--shot-start-x);
+102 -26
View File
@@ -63,6 +63,10 @@ const props = defineProps({
type: Number,
default: 5,
},
stableShotEffect: {
type: Boolean,
default: false,
},
});
const pMode = ref(true);
@@ -73,12 +77,14 @@ const dirTimer = ref(null);
const angle = ref(null);
const circleColor = ref("");
const shotEffect = ref(null);
const pendingShotEffect = ref(null);
const hiddenRedLatestKey = ref("");
const hiddenBlueLatestKey = ref("");
const targetShaking = ref(false);
const targetRect = ref({ left: 0, top: 0, width: 0, height: 0 });
const shakeTimer = ref(null);
const instance = getCurrentInstance();
let shotEffectRequestGeneration = 0;
const ROUND_TIP_OFFSET_Y = -32;
const EXPERIENCE_TIP_OFFSET_Y = -68;
@@ -135,7 +141,7 @@ function showShotTip(team, shot) {
}, 1000);
}
function triggerShotEffect(team, shot, index) {
function triggerShotEffect(team, shot, index, viewportMode = false) {
const key = buildShotEffectKey(team, shot, index);
if (shotEffect.value?.team === "red") hiddenRedLatestKey.value = "";
@@ -149,7 +155,29 @@ function triggerShotEffect(team, shot, index) {
hiddenBlueLatestKey.value = key;
}
shotEffect.value = { key, team, shot };
shotEffect.value = { key, team, shot, viewportMode };
}
async function prepareShotEffect(team, shot, index) {
const requestGeneration = ++shotEffectRequestGeneration;
const key = buildShotEffectKey(team, shot, index);
pendingShotEffect.value = { generation: requestGeneration, team, key };
clearTipTimer();
if (team === "red") latestOne.value = null;
if (team === "blue") bluelatestOne.value = null;
const viewportMode = props.stableShotEffect
? await updateTargetRect()
: false;
if (requestGeneration !== shotEffectRequestGeneration) {
if (pendingShotEffect.value?.generation === requestGeneration) {
pendingShotEffect.value = null;
}
return;
}
pendingShotEffect.value = null;
triggerShotEffect(team, shot, index, viewportMode);
}
function completeShotEffect(key) {
@@ -178,44 +206,67 @@ function shakeTarget() {
});
}
function updateTargetRect() {
nextTick(() => {
function hasValidTargetRect(rect = targetRect.value) {
return (
Number.isFinite(Number(rect?.left)) &&
Number.isFinite(Number(rect?.top)) &&
Number(rect?.width) > 0 &&
Number(rect?.height) > 0
);
}
async function updateTargetRect() {
await nextTick();
return new Promise((resolve) => {
let settled = false;
const finish = (rect) => {
if (settled) return;
settled = true;
const isValid = hasValidTargetRect(rect);
if (isValid) {
targetRect.value = {
left: Number(rect.left),
top: Number(rect.top),
width: Number(rect.width),
height: Number(rect.height),
};
}
resolve(isValid);
};
try {
const query = instance?.proxy
? uni.createSelectorQuery().in(instance.proxy)
: uni.createSelectorQuery();
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(left) ||
!Number.isFinite(top) ||
!Number.isFinite(width) ||
!Number.isFinite(height)
) {
return;
.boundingClientRect()
.exec((result) => finish(Array.isArray(result) ? result[0] : null));
} catch {
finish(null);
}
if (width <= 0 || height <= 0) return;
targetRect.value = { left, top, width, height };
})
.exec();
});
}
function handleWindowResize() {
updateTargetRect();
void updateTargetRect();
}
function shouldHideRedHit(index) {
return !!hiddenRedLatestKey.value && index === props.scores.length - 1;
return (
(!!hiddenRedLatestKey.value || pendingShotEffect.value?.team === "red") &&
index === props.scores.length - 1
);
}
function shouldHideBlueHit(index) {
return !!hiddenBlueLatestKey.value && index === props.blueScores.length - 1;
return (
(!!hiddenBlueLatestKey.value || pendingShotEffect.value?.team === "blue") &&
index === props.blueScores.length - 1
);
}
watch(
@@ -224,14 +275,18 @@ watch(
if (newLen === oldLen + 1) {
const latestShot = props.scores[newLen - 1];
if (shouldPlayShotEffect(latestShot)) {
triggerShotEffect("red", latestShot, newLen - 1);
void prepareShotEffect("red", latestShot, newLen - 1);
} else {
shotEffectRequestGeneration += 1;
pendingShotEffect.value = null;
showShotTip("red", latestShot);
}
return;
}
if (newLen < oldLen) {
shotEffectRequestGeneration += 1;
pendingShotEffect.value = null;
latestOne.value = null;
hiddenRedLatestKey.value = "";
if (shotEffect.value?.team === "red") shotEffect.value = null;
@@ -245,14 +300,18 @@ watch(
if (newLen === oldLen + 1) {
const latestShot = props.blueScores[newLen - 1];
if (shouldPlayShotEffect(latestShot)) {
triggerShotEffect("blue", latestShot, newLen - 1);
void prepareShotEffect("blue", latestShot, newLen - 1);
} else {
shotEffectRequestGeneration += 1;
pendingShotEffect.value = null;
showShotTip("blue", latestShot);
}
return;
}
if (newLen < oldLen) {
shotEffectRequestGeneration += 1;
pendingShotEffect.value = null;
bluelatestOne.value = null;
hiddenBlueLatestKey.value = "";
if (shotEffect.value?.team === "blue") shotEffect.value = null;
@@ -411,11 +470,13 @@ async function onReceiveMessage(message) {
onMounted(() => {
uni.$on("socket-inbox", onReceiveMessage);
updateTargetRect();
void updateTargetRect();
if (uni.onWindowResize) uni.onWindowResize(handleWindowResize);
});
onBeforeUnmount(() => {
shotEffectRequestGeneration += 1;
pendingShotEffect.value = null;
if (timer.value) {
clearTimeout(timer.value);
timer.value = null;
@@ -521,6 +582,7 @@ onBeforeUnmount(() => {
</view>
</block>
<BowShotEffect
v-if="!shotEffect || !shotEffect.viewportMode"
:shot="shotEffect && shotEffect.shot"
:playKey="shotEffect ? shotEffect.key : ''"
:targetRadius="safeTargetRadius"
@@ -534,6 +596,20 @@ onBeforeUnmount(() => {
/>
<image src="https://static.shelingxingqiu.com/shootmini/static/bow-target.png" mode="widthFix" />
</view>
<BowShotEffect
v-if="shotEffect && shotEffect.viewportMode"
:shot="shotEffect.shot"
:playKey="shotEffect.key"
:targetRadius="safeTargetRadius"
:targetLeft="targetRect.left"
:targetTop="targetRect.top"
:targetWidth="targetRect.width"
:targetHeight="targetRect.height"
:hitOffsetPx="currentHitRadiusPx"
:viewportMode="true"
@impact="shakeTarget"
@complete="completeShotEffect"
/>
<view class="footer">
<PointSwitcher
:onChange="(val) => (pMode = val)"
+1 -1
View File
@@ -489,8 +489,8 @@ onShow(() => {
:totalRound="12"
:scores="playersScores.map((r) => r[user.id]).flat()"
:isSvip="isCurrentUserSvip"
:missAsZero="true"
:stop="halfRest"
stable-shot-effect
/>
<view :style="{ paddingBottom: '20px' }">
<PlayerScore
+1
View File
@@ -466,6 +466,7 @@ onBeforeUnmount(() => {
:currentRound="scores.length % 3"
:scores="scores"
:isSvip="isSvip"
stable-shot-effect
/>
<ScorePanel2 :arrows="scores" />
<ScoreResult
+101 -25
View File
@@ -67,6 +67,10 @@ const props = defineProps({
type: Number,
default: 5,
},
stableShotEffect: {
type: Boolean,
default: false,
},
});
const pMode = ref(true);
@@ -77,12 +81,14 @@ const dirTimer = ref(null);
const angle = ref(null);
const circleColor = ref("");
const shotEffect = ref(null);
const pendingShotEffect = ref(null);
const hiddenRedLatestKey = ref("");
const hiddenBlueLatestKey = ref("");
const targetShaking = ref(false);
const targetRect = ref({ left: 0, top: 0, width: 0, height: 0 });
const shakeTimer = ref(null);
const instance = getCurrentInstance();
let shotEffectRequestGeneration = 0;
const ROUND_TIP_OFFSET_Y = -32;
const EXPERIENCE_TIP_OFFSET_Y = -68;
@@ -137,7 +143,7 @@ function showShotTip(team, shootData) {
}, 1000);
}
function triggerShotEffect(team, shot, fallbackKey = "") {
function triggerShotEffect(team, shot, fallbackKey = "", viewportMode = false) {
const key = buildShotEffectKey(team, shot, fallbackKey);
if (shotEffect.value?.team === "red") hiddenRedLatestKey.value = "";
@@ -151,7 +157,29 @@ function triggerShotEffect(team, shot, fallbackKey = "") {
hiddenBlueLatestKey.value = key;
}
shotEffect.value = { key, team, shot };
shotEffect.value = { key, team, shot, viewportMode };
}
async function prepareShotEffect(team, shot, fallbackKey = "") {
const requestGeneration = ++shotEffectRequestGeneration;
const key = buildShotEffectKey(team, shot, fallbackKey);
pendingShotEffect.value = { generation: requestGeneration, team, key };
clearTipTimer();
if (team === "red") latestOne.value = null;
if (team === "blue") bluelatestOne.value = null;
const viewportMode = props.stableShotEffect
? await updateTargetRect()
: false;
if (requestGeneration !== shotEffectRequestGeneration) {
if (pendingShotEffect.value?.generation === requestGeneration) {
pendingShotEffect.value = null;
}
return;
}
pendingShotEffect.value = null;
triggerShotEffect(team, shot, fallbackKey, viewportMode);
}
function completeShotEffect(key) {
@@ -180,49 +208,74 @@ function shakeTarget() {
});
}
function updateTargetRect() {
nextTick(() => {
function hasValidTargetRect(rect = targetRect.value) {
return (
Number.isFinite(Number(rect?.left)) &&
Number.isFinite(Number(rect?.top)) &&
Number(rect?.width) > 0 &&
Number(rect?.height) > 0
);
}
async function updateTargetRect() {
await nextTick();
return new Promise((resolve) => {
let settled = false;
const finish = (rect) => {
if (settled) return;
settled = true;
const isValid = hasValidTargetRect(rect);
if (isValid) {
targetRect.value = {
left: Number(rect.left),
top: Number(rect.top),
width: Number(rect.width),
height: Number(rect.height),
};
}
resolve(isValid);
};
try {
const query = instance?.proxy
? uni.createSelectorQuery().in(instance.proxy)
: uni.createSelectorQuery();
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(left) ||
!Number.isFinite(top) ||
!Number.isFinite(width) ||
!Number.isFinite(height)
) {
return;
.boundingClientRect()
.exec((result) => finish(Array.isArray(result) ? result[0] : null));
} catch {
finish(null);
}
if (width <= 0 || height <= 0) return;
targetRect.value = { left, top, width, height };
})
.exec();
});
}
function handleWindowResize() {
updateTargetRect();
void updateTargetRect();
}
function shouldHideRedHit(index) {
return !!hiddenRedLatestKey.value && index === props.scores.length - 1;
return (
(!!hiddenRedLatestKey.value || pendingShotEffect.value?.team === "red") &&
index === props.scores.length - 1
);
}
function shouldHideBlueHit(index) {
return !!hiddenBlueLatestKey.value && index === props.blueScores.length - 1;
return (
(!!hiddenBlueLatestKey.value || pendingShotEffect.value?.team === "blue") &&
index === props.blueScores.length - 1
);
}
function showShotFlash(flash) {
const shootData = flash?.shootData;
if (!shootData) {
shotEffectRequestGeneration += 1;
pendingShotEffect.value = null;
hiddenRedLatestKey.value = "";
hiddenBlueLatestKey.value = "";
shotEffect.value = null;
@@ -231,10 +284,12 @@ function showShotFlash(flash) {
const team = flash.team === "red" ? "red" : "blue";
if (shouldPlayShotEffect(shootData, team)) {
triggerShotEffect(team, shootData, flash.key);
void prepareShotEffect(team, shootData, flash.key);
return;
}
shotEffectRequestGeneration += 1;
pendingShotEffect.value = null;
showShotTip(team, shootData);
}
@@ -250,6 +305,8 @@ watch(
() => props.scores.length,
(newLen, oldLen) => {
if (newLen > oldLen) return;
shotEffectRequestGeneration += 1;
pendingShotEffect.value = null;
latestOne.value = null;
hiddenRedLatestKey.value = "";
if (shotEffect.value?.team === "red") shotEffect.value = null;
@@ -260,6 +317,8 @@ watch(
() => props.blueScores.length,
(newLen, oldLen) => {
if (newLen > oldLen) return;
shotEffectRequestGeneration += 1;
pendingShotEffect.value = null;
bluelatestOne.value = null;
hiddenBlueLatestKey.value = "";
if (shotEffect.value?.team === "blue") shotEffect.value = null;
@@ -417,11 +476,13 @@ async function onReceiveMessage(message) {
onMounted(() => {
uni.$on("socket-inbox", onReceiveMessage);
updateTargetRect();
void updateTargetRect();
if (uni.onWindowResize) uni.onWindowResize(handleWindowResize);
});
onBeforeUnmount(() => {
shotEffectRequestGeneration += 1;
pendingShotEffect.value = null;
if (timer.value) {
clearTimeout(timer.value);
timer.value = null;
@@ -539,6 +600,7 @@ onBeforeUnmount(() => {
</view>
</block>
<BowShotEffect
v-if="!shotEffect || !shotEffect.viewportMode"
:shot="shotEffect && shotEffect.shot"
:playKey="shotEffect ? shotEffect.key : ''"
:targetRadius="safeTargetRadius"
@@ -552,6 +614,20 @@ onBeforeUnmount(() => {
/>
<image src="https://static.shelingxingqiu.com/shootmini/static/bow-target.png" mode="widthFix" />
</view>
<BowShotEffect
v-if="shotEffect && shotEffect.viewportMode"
:shot="shotEffect.shot"
:playKey="shotEffect.key"
:targetRadius="safeTargetRadius"
:targetLeft="targetRect.left"
:targetTop="targetRect.top"
:targetWidth="targetRect.width"
:targetHeight="targetRect.height"
:hitOffsetPx="currentHitRadiusPx"
:viewportMode="true"
@impact="shakeTarget"
@complete="completeShotEffect"
/>
<view class="footer">
<PointSwitcher
:onChange="(val) => (pMode = val)"
+1
View File
@@ -1417,6 +1417,7 @@ onShow(() => {
:latestShotFlash="latestShotFlash"
:redTeam="redTeam"
:blueTeam="blueTeam"
stable-shot-effect
/>
<BattleFooter
v-if="start"
+93 -23
View File
@@ -84,6 +84,10 @@ const props = defineProps({
type: Boolean,
default: false,
},
stableShotEffect: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(["shot-effect-complete"]);
@@ -98,11 +102,13 @@ const dirTimer = ref(null);
const angle = ref(null);
const circleColor = ref("");
const shotEffect = ref(null);
const pendingShotEffect = ref(null);
const hiddenLatestKey = ref("");
const targetShaking = ref(false);
const targetRect = ref({ left: 0, top: 0, width: 0, height: 0 });
const shakeTimer = ref(null);
const instance = getCurrentInstance();
let shotEffectRequestGeneration = 0;
const ROUND_TIP_OFFSET_Y = -32;
const EXPERIENCE_TIP_OFFSET_Y = -68;
@@ -245,7 +251,7 @@ function buildShotEffectKey(shot, index) {
].join("-");
}
function triggerShotEffect(shot, index) {
function triggerShotEffect(shot, index, viewportMode = false) {
const key = buildShotEffectKey(shot, index);
clearTipTimer();
latestOne.value = null;
@@ -254,9 +260,31 @@ function triggerShotEffect(shot, index) {
key,
shot,
token: props.shotEffectToken,
viewportMode,
};
}
async function prepareShotEffect(shot, index) {
const requestGeneration = ++shotEffectRequestGeneration;
const key = buildShotEffectKey(shot, index);
pendingShotEffect.value = { generation: requestGeneration, key };
clearTipTimer();
latestOne.value = null;
const viewportMode = props.stableShotEffect
? await updateTargetRect()
: false;
if (requestGeneration !== shotEffectRequestGeneration) {
if (pendingShotEffect.value?.generation === requestGeneration) {
pendingShotEffect.value = null;
}
return;
}
pendingShotEffect.value = null;
triggerShotEffect(shot, index, viewportMode);
}
function completeShotEffect(key) {
if (!shotEffect.value || shotEffect.value.key !== key) return;
@@ -272,7 +300,10 @@ function completeShotEffect(key) {
}
function shouldHideLatestHit(index) {
return !!hiddenLatestKey.value && index === props.scores.length - 1;
return (
(!!hiddenLatestKey.value || !!pendingShotEffect.value) &&
index === props.scores.length - 1
);
}
function shakeTarget() {
@@ -291,36 +322,53 @@ function shakeTarget() {
});
}
function updateTargetRect() {
nextTick(() => {
function hasValidTargetRect(rect = targetRect.value) {
return (
Number.isFinite(Number(rect?.left)) &&
Number.isFinite(Number(rect?.top)) &&
Number(rect?.width) > 0 &&
Number(rect?.height) > 0
);
}
async function updateTargetRect() {
await nextTick();
return new Promise((resolve) => {
let settled = false;
const finish = (rect) => {
if (settled) return;
settled = true;
const isValid = hasValidTargetRect(rect);
if (isValid) {
targetRect.value = {
left: Number(rect.left),
top: Number(rect.top),
width: Number(rect.width),
height: Number(rect.height),
};
}
resolve(isValid);
};
try {
const query = instance?.proxy
? uni.createSelectorQuery().in(instance.proxy)
: uni.createSelectorQuery();
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(left) ||
!Number.isFinite(top) ||
!Number.isFinite(width) ||
!Number.isFinite(height)
) {
return;
.boundingClientRect()
.exec((result) => finish(Array.isArray(result) ? result[0] : null));
} catch {
finish(null);
}
if (width <= 0 || height <= 0) return;
targetRect.value = { left, top, width, height };
})
.exec();
});
}
function handleWindowResize() {
updateTargetRect();
void updateTargetRect();
}
watch(
@@ -329,6 +377,8 @@ watch(
if (newVal.length - prevScores.value.length === 1) {
showShotTip(newVal[newVal.length - 1]);
} else if (newVal.length < prevScores.value.length) {
shotEffectRequestGeneration += 1;
pendingShotEffect.value = null;
clearTipTimer();
latestOne.value = null;
hiddenLatestKey.value = "";
@@ -349,7 +399,10 @@ watch(
const latestIndex = props.scores.length - 1;
const latestShot = props.scores[latestIndex];
if (shouldPlayShotEffect(latestShot)) {
triggerShotEffect(latestShot, latestIndex);
void prepareShotEffect(latestShot, latestIndex);
} else {
shotEffectRequestGeneration += 1;
pendingShotEffect.value = null;
}
}
);
@@ -431,11 +484,13 @@ async function onReceiveMessage(message) {
onMounted(() => {
uni.$on("socket-inbox", onReceiveMessage);
updateTargetRect();
void updateTargetRect();
if (uni.onWindowResize) uni.onWindowResize(handleWindowResize);
});
onBeforeUnmount(() => {
shotEffectRequestGeneration += 1;
pendingShotEffect.value = null;
clearTipTimer();
if (dirTimer.value) {
clearTimeout(dirTimer.value);
@@ -555,6 +610,7 @@ onBeforeUnmount(() => {
</view>
</block>
<BowShotEffect
v-if="!shotEffect || !shotEffect.viewportMode"
:shot="shotEffect && shotEffect.shot"
:playKey="shotEffect ? shotEffect.key : ''"
:targetRadius="safeTargetRadius"
@@ -567,6 +623,20 @@ onBeforeUnmount(() => {
@complete="completeShotEffect"
/>
</view>
<BowShotEffect
v-if="shotEffect && shotEffect.viewportMode"
:shot="shotEffect.shot"
:playKey="shotEffect.key"
:targetRadius="safeTargetRadius"
:targetLeft="targetRect.left"
:targetTop="targetRect.top"
:targetWidth="targetRect.width"
:targetHeight="targetRect.height"
:hitOffsetPx="currentHitRadiusPx"
:viewportMode="true"
@impact="shakeTarget"
@complete="completeShotEffect"
/>
<view class="footer">
<PointSwitcher
:onChange="(val) => (pMode = val)"
+1
View File
@@ -1330,6 +1330,7 @@ onBeforeUnmount(() => {
:activeSector="precisionRandomBlock"
:activeRing="precisionRandomRingArea"
:showSectorLabels="precisionBlocks > 0"
stable-shot-effect
@shot-effect-complete="onShotEffectComplete"
/>
<!-- <view v-if="env !== 'release'" class="highlight-test-actions">