Commit 28863a1a authored by 陈冲's avatar 陈冲

fix: 修复game5白屏,将素材整合为sprite图

parent c29b01c5
<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
import { cssAssetUrl } from '@/commons/assets.ts'
import { assetUrl, cssAssetUrl } from '@/commons/assets.ts'
import MobileStage from '@/components/MobileStage.vue'
import { useGameSocket, sendGameMessage, userJoinStatus, joinSharedRoom } from '@/composables/useGameSocket'
import LoadingView from './views/LoadingView.vue'
......@@ -45,11 +45,39 @@ const imageUrls = {
rule: cssAssetUrl('game1/rule.png')
}
const playingAssetUrls = [
assetUrl('game5/second-bg.webp'),
assetUrl('game5/paodao.webp'),
assetUrl('game5/circle-bg.webp'),
assetUrl('game5/circle.webp'),
assetUrl('game5/biaoti.webp'),
assetUrl('game5/sprites/circle-animation.png'),
assetUrl('game5/sprites/horse1-no-circle.png'),
assetUrl('game5/sprites/horse2-no-circle.png'),
assetUrl('game5/sprites/horse3-no-circle.png'),
assetUrl('game5/sprites/horse4-no-circle.png'),
]
let playingAssetsPromise: Promise<void> | undefined
function preloadPlayingAssets() {
if (!playingAssetsPromise) {
playingAssetsPromise = Promise.all(playingAssetUrls.map(url => new Promise<void>((resolve) => {
const image = new Image()
image.onload = () => resolve()
image.onerror = () => resolve()
image.src = url
}))).then(() => undefined)
}
return playingAssetsPromise
}
const wechat = $getWechat()
const currentView = ref<GameView>('loading')
const isGameStarting = ref(false)
const stageBackground = computed(() => {
if (currentView.value === 'playing') {
if (currentView.value === 'playing' || isGameStarting.value) {
return `${imageUrls.playingBg} center/cover`
}
return `${imageUrls.bg} center/cover`
......@@ -148,6 +176,7 @@ function startGameCountdown(seconds = 60) {
function showLoadingView() {
stopGameCountdown()
isGameStarting.value = false
currentView.value = 'loading'
setDivDescVisible(false)
// alert('管理员关闭了游戏房间')
......@@ -156,6 +185,7 @@ function showLoadingView() {
function resetToLoadingView() {
stopGameCountdown()
isGameStarting.value = false
if (guFrameTimer) {
window.clearTimeout(guFrameTimer)
guFrameTimer = undefined
......@@ -215,8 +245,13 @@ if (wechat) {
},
onGameStart: async () => {
showGameRule.value = false;
await mobileStageRef.value?.startCountdown()
isGameStarting.value = true
await Promise.all([
mobileStageRef.value?.startCountdown(),
preloadPlayingAssets(),
])
startGameView()
isGameStarting.value = false
},
onScoreSubmitted: (is_save, data) => {
if (!is_save) {//非保存状态下
......@@ -260,6 +295,7 @@ if (wechat) {
onMounted(() => {
document.title = '互动游戏 - 圈彩纳福'
preloadPlayingAssets()
if (token.value) {
window.addEventListener('beforeunload', confirmRefresh)
}
......
......@@ -16,16 +16,14 @@ const emit = defineEmits<{
}>()
// ── 圈圈丢出动画帧 (31帧, 原图325×733 → 渲染宽165) ──
const circleAnimFrames = Array.from({ length: 31 }, (_, i) =>
assetUrl(`game5/circle-animation/qcnf-quan_${String(i).padStart(2, '0')}.png`)
)
const circleAnimSprite = assetUrl('game5/sprites/circle-animation.png')
// ── 四种马的帧 (各21帧, 原图196×204 → 渲染180×160) ──
const horseFrames: Record<number, string[]> = {}
for (let h = 1; h <= 4; h++) {
horseFrames[h] = Array.from({ length: 21 }, (_, i) =>
assetUrl(`game5/horse${h}/no-circle/qcnf-ma${h}_${String(i).padStart(2, '0')}.png`)
)
const horseSprites: Record<number, string> = {
1: assetUrl('game5/sprites/horse1-no-circle.png'),
2: assetUrl('game5/sprites/horse2-no-circle.png'),
3: assetUrl('game5/sprites/horse3-no-circle.png'),
4: assetUrl('game5/sprites/horse4-no-circle.png'),
}
// ── 布局常量 ──
......@@ -57,6 +55,12 @@ const HORSE_FRAME_TICK = 3
const CIRCLE_CENTER_X = 375
const CIRCLE_CENTER_Y = STAGE_HEIGHT - 72 - 345 + 345 / 2 // 1379.5
const CIRCLE_ANIM_WIDTH = 165
const CIRCLE_SPRITE_COLS = 8
const HORSE_SPRITE_COLS = 7
function spritePosition(frameIdx: number, cols: number, frameWidth: number, frameHeight: number) {
return `-${(frameIdx % cols) * frameWidth}px -${Math.floor(frameIdx / cols) * frameHeight}px`
}
const CIRCLE_ANIM_HEIGHT = Math.round(733 * (CIRCLE_ANIM_WIDTH / 325)) // ≈ 372
// ── 类型 ──
......@@ -331,19 +335,11 @@ function loop(ts: number) {
}
// ── 预加载所有帧图片,避免运行时切换帧的闪烁 ──
function preloadFrames() {
// 圈圈动画 31 帧
for (const url of circleAnimFrames) {
const img = new Image()
img.src = url
}
// 四种马各 21 帧
for (const frames of Object.values(horseFrames)) {
for (const url of frames) {
function preloadSprites() {
for (const url of [circleAnimSprite, ...Object.values(horseSprites)]) {
const img = new Image()
img.src = url
}
}
}
// ── 监听父组件倒计时,到 0 时暂停游戏 ──
......@@ -356,7 +352,7 @@ watch(() => props.countdownInterval, (val) => {
})
onMounted(() => {
preloadFrames()
preloadSprites()
// 先启动音频上下文(需用户首次交互后 resume,此处尝试提前激活)
try { getAudioContext() } catch { /* ignore */ }
// MobileStage 已完成 3-2-1,游戏直接开始
......@@ -406,11 +402,13 @@ onBeforeUnmount(() => {
:class="{ 'horse-dying': horse.dying }"
:style="{ transform: `translateX(${horse.x}px)` }"
>
<img
class="horse-img"
:src="horseFrames[horse.type]?.[horse.frameIdx] ?? ''"
alt="horse"
/>
<div
class="horse-sprite"
:style="{
backgroundImage: `url(${horseSprites[horse.type]})`,
backgroundPosition: spritePosition(horse.frameIdx, HORSE_SPRITE_COLS, HORSE_WIDTH, HORSE_HEIGHT),
}"
></div>
<div class="horse-shadow" :class="{ 'shadow-dying': horse.dying }"></div>
</div>
</template>
......@@ -431,7 +429,8 @@ onBeforeUnmount(() => {
transform: `translate(${CIRCLE_CENTER_X - CIRCLE_ANIM_WIDTH / 2}px, ${circleThrow.y - CIRCLE_ANIM_HEIGHT / 2}px)`,
width: CIRCLE_ANIM_WIDTH + 'px',
height: CIRCLE_ANIM_HEIGHT + 'px',
backgroundImage: `url(${circleAnimFrames[circleThrow.frameIdx]})`,
backgroundImage: `url(${circleAnimSprite})`,
backgroundPosition: spritePosition(circleThrow.frameIdx, CIRCLE_SPRITE_COLS, CIRCLE_ANIM_WIDTH, CIRCLE_ANIM_HEIGHT),
}"
></div>
......@@ -601,10 +600,11 @@ onBeforeUnmount(() => {
will-change: transform;
}
.horse-img {
.horse-sprite {
width: 100%;
height: 100%;
object-fit: cover;
background-size: 1260px 600px;
background-repeat: no-repeat;
}
.horse-dying {
......@@ -664,12 +664,11 @@ onBeforeUnmount(() => {
position: absolute;
left: 0;
top: 0;
background-position: center;
background-size: contain;
background-size: 1320px 1488px;
background-repeat: no-repeat;
z-index: 10;
pointer-events: none;
will-change: transform, background-image;
will-change: transform, background-position;
image-rendering: auto;
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment