Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
F
fc-minigame
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
陈冲
fc-minigame
Commits
fedc7c9b
Commit
fedc7c9b
authored
Jun 12, 2026
by
董政锦
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: 测回;
parent
dbc63f3d
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
651 additions
and
255 deletions
+651
-255
PlayingView.vue
frontend-h5/src/pages/game3/views/PlayingView.vue
+454
-102
Game4.vue
frontend-h5/src/pages/game4/Game4.vue
+197
-153
No files found.
frontend-h5/src/pages/game3/views/PlayingView.vue
View file @
fedc7c9b
<
script
setup
lang=
"ts"
>
import
{
onBeforeUnmount
,
onMounted
,
ref
,
watch
,
nextTick
,
computed
}
from
'vue'
;
import
{
assetUrl
}
from
'@/commons/assets.ts'
import
{
playGame6Music
}
from
'@/commons/music'
import
{
onBeforeUnmount
,
onMounted
,
ref
,
watch
,
nextTick
,
computed
,
}
from
"vue"
;
import
{
assetUrl
}
from
"@/commons/assets.ts"
;
// import { playGame6Music } from '@/commons/music'
const
props
=
defineProps
<
{
imageUrls
:
Record
<
string
,
string
>
countdownInterval
:
number
isDivDescVisible
:
boolean
tick
:
number
rank
:
number
imageUrls
:
Record
<
string
,
string
>
;
countdownInterval
:
number
;
isDivDescVisible
:
boolean
;
tick
:
number
;
rank
:
number
;
}
>
();
const
emit
=
defineEmits
<
{
touch
:
[
mole
:
boolean
]
scoreChange
:
[
delta
:
number
]
// touch: [mole: boolean]
scoreChange
:
[
delta
:
number
];
rankClose
:
[];
}
>
();
// ========== 常量 ==========
...
...
@@ -25,56 +33,84 @@ const HORSE_BOTTOM_PCT = 19.11;
const
ITEM_SIZE
=
80
;
const
GAME_DURATION
=
60
;
const
LEFT_LANE_X
=
170
;
const
RIGHT_LANE_X
=
470
;
// 设计基准跑道 X 位置(750px 设计稿下的值,窄屏设备上会自动居中偏移)
const
DESIGN_LEFT_LANE_X
=
150
;
const
DESIGN_RIGHT_LANE_X
=
500
;
// 运行时根据视口计算的真实 X 位置(适配不同屏幕宽度)
const
leftLaneX
=
ref
(
DESIGN_LEFT_LANE_X
);
const
rightLaneX
=
ref
(
DESIGN_RIGHT_LANE_X
);
/** 按视口宽度计算 750px 设计区域在 content-bg 中的居中偏移 */
const
updateLanePositions
=
()
=>
{
const
vw
=
window
.
visualViewport
?.
width
??
window
.
innerWidth
;
const
vh
=
window
.
visualViewport
?.
height
??
window
.
innerHeight
;
// MobileStage contain 模式下 scale = min(scaleX, scaleY),手机通常由高度决定
const
scaleY
=
vh
/
1624
;
if
(
!
scaleY
||
scaleY
<=
0
)
return
;
const
stageViewportWidth
=
vw
/
scaleY
;
const
offset
=
Math
.
max
(
0
,
(
stageViewportWidth
-
750
)
/
2
);
leftLaneX
.
value
=
DESIGN_LEFT_LANE_X
+
offset
;
rightLaneX
.
value
=
DESIGN_RIGHT_LANE_X
+
offset
;
};
// ========== 马帧 ==========
const
HORSE_FRAME_COUNT
=
21
;
const
horseFrames
=
Array
.
from
({
length
:
HORSE_FRAME_COUNT
},
(
_
,
i
)
=>
assetUrl
(
`game3/horse/1-ma_
${
String
(
i
).
padStart
(
2
,
'0'
)}
.png`
)
assetUrl
(
`game3/horse/1-ma_
${
String
(
i
).
padStart
(
2
,
"0"
)}
.png`
),
);
// ========== 金币/炸弹帧 ==========
const
JINBI_FRAME_COUNT
=
25
;
const
jinbiFrames
=
Array
.
from
({
length
:
JINBI_FRAME_COUNT
},
(
_
,
i
)
=>
assetUrl
(
`game3/jinbi/1-yuanbao_
${
String
(
i
).
padStart
(
2
,
'0'
)}
.png`
)
assetUrl
(
`game3/jinbi/1-yuanbao_
${
String
(
i
).
padStart
(
2
,
"0"
)}
.png`
),
);
const
BOM_FRAME_COUNT
=
28
;
const
bomFrames
=
Array
.
from
({
length
:
BOM_FRAME_COUNT
},
(
_
,
i
)
=>
assetUrl
(
`game3/bom/1-zha_
${
String
(
i
).
padStart
(
2
,
'0'
)}
.png`
)
assetUrl
(
`game3/bom/1-zha_
${
String
(
i
).
padStart
(
2
,
"0"
)}
.png`
),
);
const
fudaiUrl
=
assetUrl
(
'game3/icon_fudai.webp'
);
const
dileiUrl
=
assetUrl
(
'game3/icon_dilei.webp'
);
const
fudaiUrl
=
assetUrl
(
"game3/icon_fudai.webp"
);
const
dileiUrl
=
assetUrl
(
"game3/icon_dilei.webp"
);
// ========== 预加载碰撞动画帧(解决 4G 慢网下动画图片未加载的问题) ==========
const
preloadFrames
=
(
urls
:
string
[])
=>
{
for
(
const
url
of
urls
)
{
const
img
=
new
Image
();
img
.
src
=
url
;
}
};
preloadFrames
(
jinbiFrames
);
preloadFrames
(
bomFrames
);
// ========== 马状态 ==========
const
horseLane
=
ref
<
'left'
|
'right'
>
(
'left'
);
const
horseLane
=
ref
<
"left"
|
"right"
>
(
"left"
);
const
horseFrameIndex
=
ref
(
0
);
const
horseX
=
ref
(
LEFT_LANE_X
);
const
horseTargetX
=
ref
(
LEFT_LANE_X
);
const
horseX
=
ref
(
leftLaneX
.
value
);
const
horseTargetX
=
ref
(
leftLaneX
.
value
);
const
switchToLeft
=
()
=>
{
if
(
horseLane
.
value
!==
'left'
)
{
horseLane
.
value
=
'left'
;
horseTargetX
.
value
=
LEFT_LANE_X
;
if
(
horseLane
.
value
!==
"left"
)
{
horseLane
.
value
=
"left"
;
horseTargetX
.
value
=
leftLaneX
.
value
;
}
};
const
switchToRight
=
()
=>
{
if
(
horseLane
.
value
!==
'right'
)
{
horseLane
.
value
=
'right'
;
horseTargetX
.
value
=
RIGHT_LANE_X
;
if
(
horseLane
.
value
!==
"right"
)
{
horseLane
.
value
=
"right"
;
horseTargetX
.
value
=
rightLaneX
.
value
;
}
};
// ========== 键盘控制 ==========
const
handleKeydown
=
(
e
:
KeyboardEvent
)
=>
{
if
(
!
props
.
isDivDescVisible
)
return
;
if
(
e
.
key
===
'ArrowLeft'
||
e
.
key
===
'a'
||
e
.
key
===
'A'
)
{
if
(
e
.
key
===
"ArrowLeft"
||
e
.
key
===
"a"
||
e
.
key
===
"A"
)
{
switchToLeft
();
}
else
if
(
e
.
key
===
'ArrowRight'
||
e
.
key
===
'd'
||
e
.
key
===
'D'
)
{
}
else
if
(
e
.
key
===
"ArrowRight"
||
e
.
key
===
"d"
||
e
.
key
===
"D"
)
{
switchToRight
();
}
};
...
...
@@ -83,8 +119,8 @@ const handleKeydown = (e: KeyboardEvent) => {
const
roadOffset
=
ref
(
0
);
let
roadRafId
:
number
|
undefined
;
let
roadLastTs
=
0
;
const
BASE_ROAD_SPEED
=
8
0
;
const
MAX_ROAD_SPEED
=
4
00
;
const
BASE_ROAD_SPEED
=
20
0
;
const
MAX_ROAD_SPEED
=
7
00
;
const
gameElapsed
=
ref
(
0
);
const
currentRoadSpeed
=
ref
(
BASE_ROAD_SPEED
);
...
...
@@ -105,9 +141,13 @@ const startRoadScroll = () => {
gameElapsed
.
value
=
(
ts
-
startTs
)
/
1000
;
const
progress
=
Math
.
min
(
gameElapsed
.
value
/
GAME_DURATION
,
1
);
currentRoadSpeed
.
value
=
BASE_ROAD_SPEED
+
(
MAX_ROAD_SPEED
-
BASE_ROAD_SPEED
)
*
progress
;
currentRoadSpeed
.
value
=
BASE_ROAD_SPEED
+
(
MAX_ROAD_SPEED
-
BASE_ROAD_SPEED
)
*
progress
;
roadOffset
.
value
+=
currentRoadSpeed
.
value
*
dt
/
1000
;
roadOffset
.
value
+=
(
currentRoadSpeed
.
value
*
dt
)
/
1000
;
if
(
roadOffset
.
value
>=
ROAD_HEIGHT
)
{
roadOffset
.
value
-=
ROAD_HEIGHT
;
}
roadRafId
=
requestAnimationFrame
(
tick
);
};
...
...
@@ -122,121 +162,187 @@ const stopRoadScroll = () => {
};
// ========== 持续道具生成系统 ==========
type
ItemKind
=
'coin'
|
'bomb'
;
type
ItemKind
=
"coin"
|
"bomb"
;
interface
RoadItem
{
id
:
number
;
kind
:
ItemKind
;
lane
:
'left'
|
'right'
;
lane
:
"left"
|
"right"
;
y
:
number
;
collected
:
boolean
;
colliding
:
boolean
;
// 防止同一帧内重复触发碰撞特效
animFrame
:
number
;
animTimer
:
ReturnType
<
typeof
setTimeout
>
|
undefined
;
}
const
roadItems
=
ref
<
RoadItem
[]
>
([]);
const
effectAnims
=
ref
<
{
id
:
number
;
kind
:
ItemKind
;
frame
:
number
;
x
:
number
;
y
:
number
}[]
>
([]);
const
effectAnims
=
ref
<
{
id
:
number
;
kind
:
ItemKind
;
frame
:
number
;
x
:
number
;
y
:
number
}[]
>
([]);
interface
ScoreEffect
{
id
:
number
;
x
:
number
;
y
:
number
;
score
:
number
;
// 正数表示加分,负数表示扣分
}
const
scoreEffects
=
ref
<
ScoreEffect
[]
>
([]);
let
scoreEffectIdSeq
=
0
;
let
itemIdCounter
=
0
;
let
lastItemY
=
0
;
const
MIN_ITEM_SPACING
=
250
;
const
MAX_ITEM_SPACING
=
400
;
const
ITEM_GEN_INTERVAL
=
1500
;
let
genTimerId
:
ReturnType
<
typeof
setTimeout
>
|
undefined
;
function
seededRandom
(
seed
:
number
)
{
const
x
=
Math
.
sin
(
seed
*
12.9898
+
78.233
)
*
43758.5453
;
return
x
-
Math
.
floor
(
x
);
}
function
scheduleNextItem
()
{
if
(
!
props
.
isDivDescVisible
)
return
;
// 游戏越接近尾声,生成间隔越短(1800ms → 350ms),加上随机抖动
const
progress
=
Math
.
min
(
gameElapsed
.
value
/
GAME_DURATION
,
1
);
const
base
=
1800
-
1450
*
progress
;
const
jitter
=
(
Math
.
random
()
-
0.5
)
*
800
;
// ±400ms 随机
const
interval
=
Math
.
max
(
250
,
base
+
jitter
);
genTimerId
=
setTimeout
(
generateNewItem
,
interval
);
}
function
generateNewItem
()
{
if
(
!
props
.
isDivDescVisible
)
return
;
const
seed
=
Date
.
now
()
+
itemIdCounter
;
const
kind
:
ItemKind
=
seededRandom
(
seed
)
<
0.55
?
'coin'
:
'bomb'
;
const
lane
:
'left'
|
'right'
=
seededRandom
(
seed
+
1000
)
<
0.5
?
'left'
:
'right'
;
const
spacing
=
MIN_ITEM_SPACING
+
seededRandom
(
seed
+
2000
)
*
(
MAX_ITEM_SPACING
-
MIN_ITEM_SPACING
);
const
startY
=
-
ITEM_SIZE
;
const
kind
:
ItemKind
=
seededRandom
(
seed
)
<
0.75
?
"coin"
:
"bomb"
;
// 概率分配车道,但避免与前一个同车道道具太近
let
lane
:
"left"
|
"right"
=
seededRandom
(
seed
+
1000
)
<
0.5
?
"left"
:
"right"
;
// 找到该车道还在顶部附近(y
<
200
)的未收集道具
const
hasNearbyInLane
=
(
ln
:
"left"
|
"right"
)
=>
roadItems
.
value
.
some
((
it
)
=>
!
it
.
collected
&&
it
.
lane
===
ln
&&
it
.
y
<
200
);
if
(
hasNearbyInLane
(
lane
)
&&
!
hasNearbyInLane
(
lane
===
"left"
?
"right"
:
"left"
)
)
{
// 首选车道已有最近道具,强制换到另一车道
lane
=
lane
===
"left"
?
"right"
:
"left"
;
}
const
newItem
:
RoadItem
=
{
id
:
itemIdCounter
++
,
kind
,
lane
,
y
:
startY
,
y
:
-
ITEM_SIZE
,
collected
:
false
,
colliding
:
false
,
animFrame
:
0
,
animTimer
:
undefined
,
};
roadItems
.
value
.
push
(
newItem
);
setTimeout
(
generateNewItem
,
ITEM_GEN_INTERVAL
);
scheduleNextItem
(
);
}
function
stopItemGen
()
{
if
(
genTimerId
)
{
clearTimeout
(
genTimerId
);
genTimerId
=
undefined
;
}
}
// 道具 y 已是 screen-space(content-bg 坐标系),清理逻辑直接判断
function
cleanupOffscreenItems
()
{
const
screenBottom
=
roadOffset
.
value
+
ROAD_HEIGHT
;
roadItems
.
value
=
roadItems
.
value
.
filter
(
item
=>
{
const
itemBottom
=
item
.
y
+
ITEM_SIZE
;
return
itemBottom
>
roadOffset
.
value
-
100
&&
item
.
y
<
screenBottom
+
100
;
roadItems
.
value
=
roadItems
.
value
.
filter
((
item
)
=>
{
// item.y 超出 content-bg 底部即删除,超出顶部也删除
return
item
.
y
<
ROAD_HEIGHT
&&
item
.
y
>
-
ITEM_SIZE
;
});
}
// ========== 碰撞检测 ==========
const
horseBottomY
=
ROAD_HEIGHT
*
(
HORSE_BOTTOM_PCT
/
100
);
const
horseTopY
=
horseBottomY
-
HORSE_H
;
// 马匹在屏幕上的Y坐标(从 content-bg 顶部算起,horse 使用 bottom: 19.11% 定位)
const
horseCSSBottom
=
ROAD_HEIGHT
*
(
1
-
HORSE_BOTTOM_PCT
/
100
);
const
horseCSSTop
=
horseCSSBottom
-
HORSE_H
;
const
checkCollisions
=
()
=>
{
const
horseCenterX
=
horseX
.
value
+
HORSE_W
/
2
;
const
horseTopCenterY
=
horseTopY
+
HORSE_H
*
0.15
;
const
horseTopCenterY
=
horseCSSTop
+
HORSE_H
*
0.15
;
const
horseRadius
=
HORSE_W
*
0.35
;
for
(
const
item
of
roadItems
.
value
)
{
if
(
item
.
collected
)
continue
;
if
(
item
.
collected
||
item
.
colliding
)
continue
;
const
itemX
=
item
.
lane
===
'left'
?
LEFT_LANE_X
:
RIGHT_LANE_X
;
const
itemVisualY
=
item
.
y
;
const
itemX
=
item
.
lane
===
"left"
?
leftLaneX
.
value
:
rightLaneX
.
value
;
// item.y 已经是屏幕坐标(content-bg 内),无需加 roadOffset
const
itemCenterX
=
itemX
+
ITEM_SIZE
/
2
;
const
itemCenterY
=
itemVisualY
+
ITEM_SIZE
/
2
;
const
itemCenterY
=
item
.
y
+
ITEM_SIZE
/
2
;
const
dist
=
Math
.
hypot
(
horseCenterX
-
itemCenterX
,
horseTopCenterY
-
itemCenterY
);
const
dist
=
Math
.
hypot
(
horseCenterX
-
itemCenterX
,
horseTopCenterY
-
itemCenterY
,
);
if
(
dist
<
horseRadius
+
ITEM_SIZE
*
0.35
)
{
// 立即标记碰撞中,防止后续帧重复触发
item
.
colliding
=
true
;
// 先启动碰撞特效动画,延迟隐藏道具让动画和道具短暂重叠过渡
playCollectEffect
(
item
,
itemX
,
item
.
y
);
setTimeout
(()
=>
{
item
.
collected
=
true
;
playCollectEffect
(
item
,
itemX
,
itemVisualY
);
},
100
);
}
}
};
const
playCollectEffect
=
(
item
:
RoadItem
,
x
:
number
,
y
:
number
)
=>
{
if
(
item
.
kind
===
'coin'
)
{
emit
(
'scoreChange'
,
5
);
playGame6Music
(
1
);
const
isCoin
=
item
.
kind
===
"coin"
;
const
delta
=
isCoin
?
5
:
-
3
;
if
(
isCoin
)
{
emit
(
"scoreChange"
,
5
);
}
else
{
emit
(
'scoreChange'
,
-
3
);
playGame6Music
(
2
);
emit
(
"scoreChange"
,
-
3
);
}
const
animId
=
Date
.
now
()
+
Math
.
random
();
const
totalFrames
=
item
.
kind
===
'coin'
?
JINBI_FRAME_COUNT
:
BOM_FRAME_COUNT
;
const
totalFrames
=
isCoin
?
JINBI_FRAME_COUNT
:
BOM_FRAME_COUNT
;
const
frameDuration
=
1000
/
totalFrames
;
effectAnims
.
value
.
push
({
id
:
animId
,
kind
:
item
.
kind
,
frame
:
0
,
x
,
y
});
// 碰撞动画(大幅放大尺寸,x 往右偏移让动画居中于视觉碰撞点)
effectAnims
.
value
.
push
({
id
:
animId
,
kind
:
item
.
kind
,
frame
:
0
,
x
:
x
+
65
,
y
,
});
let
frameIdx
=
0
;
const
tick
=
()
=>
{
frameIdx
++
;
if
(
frameIdx
>=
totalFrames
)
{
effectAnims
.
value
=
effectAnims
.
value
.
filter
(
a
=>
a
.
id
!==
animId
);
effectAnims
.
value
=
effectAnims
.
value
.
filter
((
a
)
=>
a
.
id
!==
animId
);
return
;
}
const
anim
=
effectAnims
.
value
.
find
(
a
=>
a
.
id
===
animId
);
const
anim
=
effectAnims
.
value
.
find
((
a
)
=>
a
.
id
===
animId
);
if
(
anim
)
anim
.
frame
=
frameIdx
;
item
.
animTimer
=
setTimeout
(
tick
,
frameDuration
);
};
item
.
animTimer
=
setTimeout
(
tick
,
frameDuration
);
// 积分飘字效果(参考 game5 套中圈 "+10" 动画)
const
effectId
=
++
scoreEffectIdSeq
;
scoreEffects
.
value
.
push
({
id
:
effectId
,
x
:
x
+
ITEM_SIZE
/
2
+
25
,
y
:
y
,
score
:
delta
,
});
setTimeout
(()
=>
{
const
idx
=
scoreEffects
.
value
.
findIndex
((
e
)
=>
e
.
id
===
effectId
);
if
(
idx
>=
0
)
scoreEffects
.
value
.
splice
(
idx
,
1
);
},
800
);
};
// ========== 主循环 ==========
...
...
@@ -261,31 +367,35 @@ const startMainLoop = () => {
mainLastTs
=
ts
;
horseFrameAccum
+=
dt
;
while
(
horseFrameAccum
>=
48
)
{
horseFrameAccum
-=
48
;
const
horseInterval
=
(
80
*
BASE_ROAD_SPEED
)
/
currentRoadSpeed
.
value
;
while
(
horseFrameAccum
>=
horseInterval
)
{
horseFrameAccum
-=
horseInterval
;
horseFrameIndex
.
value
=
(
horseFrameIndex
.
value
+
1
)
%
HORSE_FRAME_COUNT
;
}
const
lerpFactor
=
1
-
Math
.
pow
(
0.001
,
dt
/
1000
);
horseX
.
value
+=
(
horseTargetX
.
value
-
horseX
.
value
)
*
lerpFactor
;
checkCollisions
();
// 先移动所有道具
for
(
const
item
of
roadItems
.
value
)
{
if
(
!
item
.
collected
)
{
item
.
y
+=
currentRoadSpeed
.
value
*
dt
/
1000
;
item
.
y
+=
(
currentRoadSpeed
.
value
*
dt
)
/
1000
;
}
}
// 清理已离开屏幕的道具,避免 roadOffset 回绕后旧道具闪现
cleanupOffscreenItems
();
// 最后再检测碰撞,确保只检测屏幕内确实可见的道具
checkCollisions
();
mainRafId
=
requestAnimationFrame
(
tick
);
};
mainRafId
=
requestAnimationFrame
(
tick
);
perSecondTimer
=
setInterval
(()
=>
{
if
(
props
.
isDivDescVisible
)
{
emit
(
'scoreChange'
,
2
);
emit
(
"scoreChange"
,
2
);
}
},
1000
);
};
...
...
@@ -304,58 +414,91 @@ const stopMainLoop = () => {
// ========== 生命周期 ==========
onMounted
(()
=>
{
window
.
addEventListener
(
'keydown'
,
handleKeydown
);
window
.
addEventListener
(
"keydown"
,
handleKeydown
);
window
.
addEventListener
(
"resize"
,
updateLanePositions
);
window
.
visualViewport
?.
addEventListener
(
"resize"
,
updateLanePositions
);
updateLanePositions
();
// 初始化马匹在左侧跑道(使用计算后的实际 X 位置)
horseX
.
value
=
leftLaneX
.
value
;
horseTargetX
.
value
=
leftLaneX
.
value
;
nextTick
(()
=>
{
startRoadScroll
();
startMainLoop
();
setTimeout
(
generateNewItem
,
1000
);
scheduleNextItem
(
);
});
});
onBeforeUnmount
(()
=>
{
window
.
removeEventListener
(
'keydown'
,
handleKeydown
);
window
.
removeEventListener
(
"keydown"
,
handleKeydown
);
window
.
removeEventListener
(
"resize"
,
updateLanePositions
);
window
.
visualViewport
?.
removeEventListener
(
"resize"
,
updateLanePositions
);
stopRoadScroll
();
stopMainLoop
();
stopItemGen
();
for
(
const
item
of
roadItems
.
value
)
{
if
(
item
.
animTimer
)
clearTimeout
(
item
.
animTimer
);
}
roadItems
.
value
=
[];
effectAnims
.
value
=
[];
scoreEffects
.
value
=
[];
});
watch
(()
=>
props
.
isDivDescVisible
,
(
visible
)
=>
{
watch
(
()
=>
props
.
isDivDescVisible
,
(
visible
)
=>
{
if
(
visible
)
{
nextTick
(()
=>
{
roadItems
.
value
=
[];
effectAnims
.
value
=
[];
scoreEffects
.
value
=
[];
roadOffset
.
value
=
0
;
gameElapsed
.
value
=
0
;
updateLanePositions
();
horseX
.
value
=
leftLaneX
.
value
;
horseTargetX
.
value
=
leftLaneX
.
value
;
startRoadScroll
();
startMainLoop
();
setTimeout
(
generateNewItem
,
1000
);
scheduleNextItem
(
);
});
}
else
{
stopRoadScroll
();
stopMainLoop
();
stopItemGen
();
for
(
const
item
of
roadItems
.
value
)
{
if
(
item
.
animTimer
)
clearTimeout
(
item
.
animTimer
);
}
roadItems
.
value
=
[];
effectAnims
.
value
=
[];
scoreEffects
.
value
=
[];
}
},
{
immediate
:
true
});
},
{
immediate
:
true
},
);
// 游戏结束:暂停全部动画,显示结果页
// watch(() => props.isGameOver, (over) => {
// if (over) {
// stopRoadScroll();
// stopMainLoop();
// stopItemGen();
// }
// });
</
script
>
<
template
>
<div
class=
"h5-page game-stage"
>
<div
class=
"only-bg"
></div>
<div
class=
"content-bg"
>
<div
class=
"road-track"
:style=
"
{ transform: `translateY(${roadOffset % ROAD_HEIGHT}px)` }">
<div
class=
"road-track"
:style=
"
{ transform: `translateY(${roadOffset}px)` }"
>
<div
class=
"road-inner"
></div>
<div
class=
"road-inner"
></div>
<div
class=
"road-inner"
></div>
</div>
<!-- 道具
-->
<!-- 道具(屏幕坐标系,独立于跑道滚动)
-->
<template
v-for=
"item in roadItems"
:key=
"item.id"
>
<img
v-if=
"!item.collected"
...
...
@@ -363,7 +506,7 @@ watch(() => props.isDivDescVisible, (visible) => {
class=
"road-item"
:class=
"`item-lane-$
{item.lane}`"
:style="{
top: `${item.y + roadOffset
}px`,
top: `${item.y
}px`,
}"
/>
</
template
>
...
...
@@ -377,8 +520,22 @@ watch(() => props.isDivDescVisible, (visible) => {
/>
</
template
>
<!-- 积分飘字 -->
<div
v-for=
"ef in scoreEffects"
:key=
"ef.id"
class=
"score-float"
:class=
"ef.score > 0 ? 'score-plus' : 'score-minus'"
:style=
"{ left: ef.x + 'px', top: ef.y + 'px' }"
>
{{ ef.score > 0 ? "+" : "" }}{{ ef.score }}
</div>
<!-- 马 -->
<div
class=
"horse-wrapper"
:style=
"{ left: `${horseX}px`, bottom: `${HORSE_BOTTOM_PCT}%` }"
>
<div
class=
"horse-wrapper"
:style=
"{ left: `${horseX}px`, bottom: `${HORSE_BOTTOM_PCT}%` }"
>
<img
v-for=
"(url, idx) in horseFrames"
:key=
"idx"
...
...
@@ -401,6 +558,43 @@ watch(() => props.isDivDescVisible, (visible) => {
<div
class=
"img-hammer"
></div>
<div
class=
"img-left"
@
click=
"switchToLeft"
></div>
<div
class=
"img-right"
@
click=
"switchToRight"
></div>
<!-- 游戏结束结果页覆盖层 -->
<!-- <Transition name="rank-fade">
<div v-if="isGameOver" class="rank-overlay">
<div class="ranklist-container">
<div class="txt-title">排行</div>
<div class="list-container">
<div class="row row-1">
<div class="col-1">排名</div>
<div class="col-2">用户</div>
<div class="col-3">成绩</div>
</div>
<div class="row-2">
<div class="row rank-row" v-for="(item, index) in rankList" :key="`${item.rank}-${index}`">
<div class="col-1">
<div>{{ item.rank }}</div>
</div>
<div class="col-2">
<div class="avatar" :style="item.avatar ? { backgroundImage: `url(${item.avatar})` } : {}"></div>
<div>{{ item.nickname }}</div>
</div>
<div class="col-3">{{ item.score }}</div>
</div>
</div>
<div class="row row-3">
<div class="col-1">{{ userRank > 0 ? userRank : '未上榜' }}</div>
<div class="col-2">
<div class="avatar" :style="{ backgroundImage: `url(${userAvatar})` }"></div>
<div>{{ userNickname }}</div>
</div>
<div class="col-3">{{ userScore }}</div>
</div>
</div>
</div>
<div class="img-rank-close" @click="$emit('rankClose')"></div>
</div>
</Transition> -->
</div>
</template>
...
...
@@ -418,7 +612,7 @@ watch(() => props.isDivDescVisible, (visible) => {
left
:
var
(
--stage-viewport-left
,
0
);
width
:
var
(
--stage-viewport-width
,
750px
);
height
:
421.9px
;
background
:
v-bind
(
'imageUrls.bg3'
)
center
/
cover
no-repeat
;
background
:
v-bind
(
"imageUrls.bg3"
)
center
/
cover
no-repeat
;
}
.content-bg
{
...
...
@@ -434,7 +628,7 @@ watch(() => props.isDivDescVisible, (visible) => {
.road-track
{
width
:
100%
;
position
:
absolute
;
top
:
0
;
top
:
-1202.1px
;
left
:
0
;
display
:
flex
;
flex-direction
:
column
;
...
...
@@ -443,32 +637,77 @@ watch(() => props.isDivDescVisible, (visible) => {
.road-inner
{
width
:
100%
;
height
:
1202.1px
;
background
:
v-bind
(
'imageUrls.bg4'
)
center
/
cover
no-repeat
;
background
:
v-bind
(
"imageUrls.bg4"
)
center
/
cover
no-repeat
;
flex-shrink
:
0
;
}
.road-item
{
position
:
absolute
;
width
:
80px
;
height
:
80px
;
/* width: 80px;
height: 80px; */
width
:
77px
;
height
:
96px
;
z-index
:
5
;
pointer-events
:
none
;
}
.item-lane-left
{
left
:
170px
;
/* 当 content-bg 宽于 750px 设计稿时,自动居中 750px 游戏区域 */
left
:
calc
((
var
(
--stage-viewport-width
,
750px
)
-
750px
)
/
2
+
170px
);
}
.item-lane-right
{
left
:
470px
;
left
:
calc
((
var
(
--stage-viewport-width
,
750px
)
-
750px
)
/
2
+
520px
)
;
}
.effect-anim
{
position
:
absolute
;
width
:
12
0px
;
height
:
12
0px
;
width
:
25
0px
;
height
:
25
0px
;
z-index
:
10
;
pointer-events
:
none
;
transform
:
translate
(
-50%
,
-50%
);
}
/* 积分飘字动画 */
.score-float
{
position
:
absolute
;
z-index
:
25
;
font-size
:
56px
;
font-weight
:
900
;
pointer-events
:
none
;
white-space
:
nowrap
;
transform
:
translate
(
-50%
,
-100%
);
animation
:
score-float-up
0.8s
ease-out
forwards
;
}
.score-plus
{
color
:
#ffd700
;
text-shadow
:
0
3px
8px
rgba
(
0
,
0
,
0
,
0.7
),
0
0
20px
rgba
(
255
,
215
,
0
,
0.5
);
}
.score-minus
{
color
:
#ff4444
;
text-shadow
:
0
3px
8px
rgba
(
0
,
0
,
0
,
0.7
),
0
0
20px
rgba
(
255
,
68
,
68
,
0.5
);
}
@keyframes
score-float-up
{
0
%
{
opacity
:
1
;
transform
:
translate
(
-50%
,
-100%
)
scale
(
0.5
);
}
25
%
{
opacity
:
1
;
transform
:
translate
(
-50%
,
-140%
)
scale
(
1.15
);
}
100
%
{
opacity
:
0
;
transform
:
translate
(
-50%
,
-260%
)
scale
(
0.8
);
}
}
.horse-wrapper
{
...
...
@@ -499,7 +738,7 @@ watch(() => props.isDivDescVisible, (visible) => {
top
:
174px
;
left
:
52%
;
transform
:
translate
(
-50%
,
-50%
);
background
:
v-bind
(
'imageUrls.hydt'
)
center
/
cover
;
background
:
v-bind
(
"imageUrls.hydt"
)
center
/
cover
;
}
.img-left
{
...
...
@@ -509,7 +748,7 @@ watch(() => props.isDivDescVisible, (visible) => {
left
:
144px
;
bottom
:
5.48%
;
z-index
:
20
;
background
:
v-bind
(
'imageUrls.left'
)
center
/
cover
no-repeat
;
background
:
v-bind
(
"imageUrls.left"
)
center
/
cover
no-repeat
;
cursor
:
pointer
;
}
...
...
@@ -520,14 +759,14 @@ watch(() => props.isDivDescVisible, (visible) => {
right
:
144px
;
bottom
:
5.48%
;
z-index
:
20
;
background
:
v-bind
(
'imageUrls.right'
)
center
/
cover
no-repeat
;
background
:
v-bind
(
"imageUrls.right"
)
center
/
cover
no-repeat
;
cursor
:
pointer
;
}
.img-hammer
{
width
:
148px
;
height
:
148px
;
background
:
v-bind
(
'imageUrls.hammer'
)
center
/
cover
;
background
:
v-bind
(
"imageUrls.hammer"
)
center
/
cover
;
position
:
absolute
;
left
:
50%
;
bottom
:
100px
;
...
...
@@ -555,7 +794,7 @@ watch(() => props.isDivDescVisible, (visible) => {
width
:
38px
;
height
:
44px
;
border-radius
:
50%
;
background
:
v-bind
(
'imageUrls.clock'
)
center
/
cover
;
background
:
v-bind
(
"imageUrls.clock"
)
center
/
cover
;
}
.desc-time
{
...
...
@@ -591,12 +830,125 @@ watch(() => props.isDivDescVisible, (visible) => {
transform
:
translateX
(
0
);
}
/* 游戏结束结果页覆盖层 */
.rank-overlay
{
position
:
absolute
;
inset
:
0
;
z-index
:
100
;
background
:
rgba
(
0
,
0
,
0
,
0.65
);
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
justify-content
:
center
;
}
.ranklist-container
{
width
:
550px
;
background
:
v-bind
(
"imageUrls.ranklist"
)
center
/
100%
100%
no-repeat
;
border-radius
:
16px
;
padding
:
60px
30px
30px
;
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
.txt-title
{
font-size
:
36px
;
font-weight
:
bold
;
color
:
#aa0000
;
letter-spacing
:
2px
;
margin-bottom
:
16px
;
}
.list-container
{
width
:
100%
;
text-align
:
center
;
line-height
:
56px
;
font-size
:
24px
;
.row
{
display
:
grid
;
grid-template-columns
:
100px
auto
120px
;
align-items
:
center
;
}
.row-1
{
color
:
#aa0000
;
font-weight
:
bold
;
background
:
linear-gradient
(
-84deg
,
#ffd396
0%
,
#fff3d1
53%
,
#ffd59c
100%
);
border-radius
:
12px
12px
0
0
;
}
.row-2
{
color
:
#633911
;
max-height
:
440px
;
overflow-y
:
auto
;
.
rank-row
:
nth-child
(
odd
)
{
background
:
#f2e2ae
;
}
.rank-row
:nth-child
(
even
)
{
background
:
#fceebf
;
}
}
.row-3
{
background
:
linear-gradient
(
0deg
,
#f4b64c
0%
,
#f7c96f
100%
);
border-radius
:
0
0
12px
12px
;
font-weight
:
bold
;
}
.col-2
{
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
gap
:
12px
;
.avatar
{
width
:
40px
;
height
:
40px
;
flex
:
0
0
auto
;
border-radius
:
50%
;
background-position
:
center
;
background-size
:
cover
;
}
}
}
}
.img-rank-close
{
width
:
48px
;
height
:
48px
;
margin-top
:
24px
;
border-radius
:
50%
;
background
:
v-bind
(
"imageUrls.close"
)
center
/
cover
no-repeat
;
cursor
:
pointer
;
}
.rank-fade-enter-active
{
transition
:
opacity
0.35s
ease-out
;
}
.rank-fade-leave-active
{
transition
:
opacity
0.25s
ease-in
;
}
.rank-fade-enter-from
,
.rank-fade-leave-to
{
opacity
:
0
;
}
.img-logo
{
position
:
absolute
;
top
:
46px
;
left
:
20px
;
width
:
428px
;
height
:
77px
;
background
:
v-bind
(
'imageUrls.logo'
)
center
/
cover
;
background
:
v-bind
(
"imageUrls.logo"
)
center
/
cover
;
}
</
style
>
frontend-h5/src/pages/game4/Game4.vue
View file @
fedc7c9b
<
script
setup
lang=
"ts"
>
import
{
computed
,
onBeforeUnmount
,
onMounted
,
ref
}
from
'vue'
import
{
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'
import
PlayingView
from
'./views/PlayingView.vue'
import
ScoreView
from
'./views/ScoreView.vue'
import
{
$getWechat
,
$toast
}
from
'@/commons/utils.ts'
const
gameId
=
'game4'
;
type
GameView
=
'loading'
|
'playing'
|
'score'
import
{
computed
,
onBeforeUnmount
,
onMounted
,
ref
}
from
"vue"
;
import
{
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"
;
import
PlayingView
from
"./views/PlayingView.vue"
;
import
ScoreView
from
"./views/ScoreView.vue"
;
import
{
$getWechat
,
$toast
}
from
"@/commons/utils.ts"
;
const
gameId
=
"game4"
;
type
GameView
=
"loading"
|
"playing"
|
"score"
;
const
imageUrls
=
{
bg
:
cssAssetUrl
(
'game4/h5-game4-bg.webp'
),
bg2
:
cssAssetUrl
(
'game1/bg2.png'
),
playBg
:
cssAssetUrl
(
'game4/h5-game4-bg2.webp'
),
logo
:
cssAssetUrl
(
'game4/logo.webp'
),
time
:
cssAssetUrl
(
'game4/h5-game4-clock.svg'
),
title
:
cssAssetUrl
(
'game4/h5-game4-title1.webp'
),
title2
:
cssAssetUrl
(
'game4/h5-game4-title2.webp'
),
bg
:
cssAssetUrl
(
"game4/h5-game4-bg.webp"
),
bg2
:
cssAssetUrl
(
"game1/bg2.png"
),
playBg
:
cssAssetUrl
(
"game4/h5-game4-bg2.webp"
),
logo
:
cssAssetUrl
(
"game4/logo.webp"
),
time
:
cssAssetUrl
(
"game4/h5-game4-clock.svg"
),
title
:
cssAssetUrl
(
"game4/h5-game4-title1.webp"
),
title2
:
cssAssetUrl
(
"game4/h5-game4-title2.webp"
),
// avatarBg: cssAssetUrl('game4/h5-game4-join.svg'),
userBg
:
cssAssetUrl
(
'game4/user-bg.webpp'
),
startBg
:
cssAssetUrl
(
'game4/start-bg.webp'
),
gift
:
cssAssetUrl
(
'game4/h5-game4-gift.webp'
),
avatar
:
cssAssetUrl
(
'game1/avatar.png'
),
gu
:
cssAssetUrl
(
'game4/h5-game4-horse.webp'
),
clock
:
cssAssetUrl
(
'game1/clock.png'
),
horse
:
cssAssetUrl
(
'game4/h5-game4-horse.webp'
),
yaoyiyao
:
cssAssetUrl
(
'game4/h5-game4-yaoyiyao.svg'
),
scoreBg
:
cssAssetUrl
(
'game1/score-bg.png'
),
back
:
cssAssetUrl
(
'game4/back.svg'
),
replay
:
cssAssetUrl
(
'game4/replay.svg'
),
close
:
cssAssetUrl
(
'game1/close.png'
),
rule
:
cssAssetUrl
(
'game1/rule.png'
)
}
const
wechat
=
$getWechat
()
const
currentView
=
ref
<
GameView
>
(
'loading'
)
userBg
:
cssAssetUrl
(
"game4/user-bg.webp"
),
startBg
:
cssAssetUrl
(
"game4/start-bg.webp"
),
gift
:
cssAssetUrl
(
"game4/h5-game4-gift.webp"
),
avatar
:
cssAssetUrl
(
"game1/avatar.png"
),
gu
:
cssAssetUrl
(
"game4/h5-game4-horse.webp"
),
clock
:
cssAssetUrl
(
"game1/clock.png"
),
horse
:
cssAssetUrl
(
"game4/h5-game4-horse.webp"
),
yaoyiyao
:
cssAssetUrl
(
"game4/h5-game4-yaoyiyao.svg"
),
scoreBg
:
cssAssetUrl
(
"game1/score-bg.png"
),
back
:
cssAssetUrl
(
"game4/back.svg"
),
replay
:
cssAssetUrl
(
"game4/replay.svg"
),
close
:
cssAssetUrl
(
"game1/close.png"
),
rule
:
cssAssetUrl
(
"game1/rule.png"
),
}
;
const
wechat
=
$getWechat
()
;
const
currentView
=
ref
<
GameView
>
(
"loading"
);
const
stageBackground
=
computed
(()
=>
{
if
(
currentView
.
value
===
'playing'
)
{
return
`
${
imageUrls
.
playBg
}
center/cover`
if
(
currentView
.
value
===
"playing"
)
{
return
`
${
imageUrls
.
playBg
}
center/cover`
;
}
return
`
${
imageUrls
.
bg
}
center/cover`
})
const
countdownInterval
=
ref
(
60
)
const
isDivDescVisible
=
ref
(
false
)
const
tick
=
ref
(
0
)
// 点击次数/摇动次数
const
rank
=
ref
(
0
)
// 排名
const
currentHorse
=
ref
(
imageUrls
.
horse
)
const
currentYaoyiyao
=
ref
(
imageUrls
.
yaoyiyao
)
const
token
=
ref
(
wechat
?.
token
??
''
)
const
nickname
=
ref
(
wechat
?.
nickname
??
''
)
const
avatar
=
ref
(
wechat
?.
avatar
??
''
)
const
showGameRule
=
ref
(
true
)
const
mobileStageRef
=
ref
<
InstanceType
<
typeof
MobileStage
>
|
null
>
(
null
)
const
_offSocketMessage
=
ref
<
(()
=>
void
)
|
undefined
>
()
let
gameCountdownTimer
:
ReturnType
<
typeof
window
.
setTimeout
>
|
undefined
return
`
${
imageUrls
.
bg
}
center/cover`
;
})
;
const
countdownInterval
=
ref
(
60
)
;
const
isDivDescVisible
=
ref
(
false
)
;
const
tick
=
ref
(
0
)
;
// 点击次数/摇动次数
const
rank
=
ref
(
0
)
;
// 排名
const
currentHorse
=
ref
(
imageUrls
.
horse
)
;
const
currentYaoyiyao
=
ref
(
imageUrls
.
yaoyiyao
)
;
const
token
=
ref
(
wechat
?.
token
??
""
);
const
nickname
=
ref
(
wechat
?.
nickname
??
""
);
const
avatar
=
ref
(
wechat
?.
avatar
??
""
);
const
showGameRule
=
ref
(
true
)
;
const
mobileStageRef
=
ref
<
InstanceType
<
typeof
MobileStage
>
|
null
>
(
null
)
;
const
_offSocketMessage
=
ref
<
(()
=>
void
)
|
undefined
>
()
;
let
gameCountdownTimer
:
ReturnType
<
typeof
window
.
setTimeout
>
|
undefined
;
const
confirmRefresh
=
(
event
:
BeforeUnloadEvent
)
=>
{
event
.
preventDefault
()
event
.
returnValue
=
''
}
event
.
preventDefault
();
event
.
returnValue
=
""
;
}
;
function
setDivDescVisible
(
visible
:
boolean
)
{
isDivDescVisible
.
value
=
visible
isDivDescVisible
.
value
=
visible
;
}
function
stopGameCountdown
()
{
if
(
gameCountdownTimer
)
{
window
.
clearTimeout
(
gameCountdownTimer
)
gameCountdownTimer
=
undefined
window
.
clearTimeout
(
gameCountdownTimer
);
gameCountdownTimer
=
undefined
;
}
}
...
...
@@ -82,110 +87,106 @@ function submitScore(save: boolean = false) {
if
(
save
)
{
// item_num 游戏项目(1到6)
// wechat 原始token
sendGameMessage
(
'submit_score_save'
,
{
sendGameMessage
(
"submit_score_save"
,
{
score
:
tick
.
value
,
wechat
:
wechat
.
token_origin
,
item_num
:
4
,
nickname
:
wechat
.
nickname
,
avatar
:
wechat
.
avatar
,
rank
:
rank
.
value
,
})
});
}
else
{
sendGameMessage
(
'submit_score'
,
{
score
:
tick
.
value
})
sendGameMessage
(
"submit_score"
,
{
score
:
tick
.
value
});
}
}
function
startGameCountdown
(
seconds
=
60
)
{
stopGameCountdown
()
countdownInterval
.
value
=
seconds
setDivDescVisible
(
true
)
stopGameCountdown
();
countdownInterval
.
value
=
seconds
;
setDivDescVisible
(
true
);
const
tickCountdown
=
()
=>
{
countdownInterval
.
value
-=
1
countdownInterval
.
value
-=
1
;
if
(
countdownInterval
.
value
<=
0
)
{
countdownInterval
.
value
=
0
gameCountdownTimer
=
undefined
setDivDescVisible
(
false
)
submitScore
(
true
)
showScoreView
()
return
countdownInterval
.
value
=
0
;
gameCountdownTimer
=
undefined
;
setDivDescVisible
(
false
);
submitScore
(
true
);
showScoreView
();
return
;
}
gameCountdownTimer
=
window
.
setTimeout
(
tickCountdown
,
1000
)
}
gameCountdownTimer
=
window
.
setTimeout
(
tickCountdown
,
1000
);
};
gameCountdownTimer
=
window
.
setTimeout
(
tickCountdown
,
1000
)
gameCountdownTimer
=
window
.
setTimeout
(
tickCountdown
,
1000
);
}
function
syncGameCountdown
(
seconds
:
number
)
{
const
syncedSeconds
=
Math
.
max
(
0
,
Math
.
floor
(
seconds
))
if
(
countdownInterval
.
value
-
syncedSeconds
<=
1
)
return
const
syncedSeconds
=
Math
.
max
(
0
,
Math
.
floor
(
seconds
));
if
(
countdownInterval
.
value
-
syncedSeconds
<=
1
)
return
;
if
(
syncedSeconds
===
0
)
{
countdownInterval
.
value
=
0
return
countdownInterval
.
value
=
0
;
return
;
}
startGameCountdown
(
syncedSeconds
)
startGameCountdown
(
syncedSeconds
);
}
function
showLoadingView
()
{
stopGameCountdown
()
currentView
.
value
=
'loading'
setDivDescVisible
(
false
)
stopGameCountdown
();
currentView
.
value
=
"loading"
;
setDivDescVisible
(
false
);
// alert('管理员关闭了游戏房间')
$toast
(
'管理员关闭了游戏房间'
)
$toast
(
"管理员关闭了游戏房间"
);
}
function
resetToLoadingView
()
{
stopGameCountdown
()
tick
.
value
=
0
rank
.
value
=
0
countdownInterval
.
value
=
60
currentHorse
.
value
=
imageUrls
.
horse
currentYaoyiyao
.
value
=
imageUrls
.
yaoyiyao
userJoinStatus
.
value
=
false
currentView
.
value
=
'loading'
setDivDescVisible
(
false
)
stopGameCountdown
();
tick
.
value
=
0
;
rank
.
value
=
0
;
countdownInterval
.
value
=
60
;
currentHorse
.
value
=
imageUrls
.
horse
;
currentYaoyiyao
.
value
=
imageUrls
.
yaoyiyao
;
userJoinStatus
.
value
=
false
;
currentView
.
value
=
"loading"
;
setDivDescVisible
(
false
);
}
function
startGameView
()
{
stopGameCountdown
()
tick
.
value
=
0
currentHorse
.
value
=
imageUrls
.
horse
currentView
.
value
=
'playing'
startGameCountdown
(
60
)
stopGameCountdown
();
tick
.
value
=
0
;
currentHorse
.
value
=
imageUrls
.
horse
;
currentView
.
value
=
"playing"
;
startGameCountdown
(
60
);
}
function
showScoreView
()
{
stopGameCountdown
()
setDivDescVisible
(
false
)
currentView
.
value
=
'score'
stopGameCountdown
();
setDivDescVisible
(
false
);
currentView
.
value
=
"score"
;
}
function
replayGame
()
{
startGameView
()
startGameView
();
}
function
backToWaiting
()
{
}
function
handleRoomBack
()
{
function
backToWaiting
()
{}
}
function
handleRoomBack
()
{
}
const
touchHandler
=
()
=>
{
tick
.
value
+=
5
submitScore
()
}
tick
.
value
+=
5
;
submitScore
();
}
;
const
showGameRuleHandler
=
()
=>
{
showGameRule
.
value
=
!
showGameRule
.
value
}
showGameRule
.
value
=
!
showGameRule
.
value
;
}
;
if
(
wechat
)
{
const
{
offSocketMessage
}
=
useGameSocket
({
gameId
,
auth
:
{
...
...
@@ -193,59 +194,75 @@ if (wechat) {
},
onConnect
:
()
=>
{
window
.
setTimeout
(()
=>
{
const
joined
=
joinSharedRoom
(
nickname
.
value
,
token
.
value
,
avatar
.
value
,
gameId
)
},
200
)
const
joined
=
joinSharedRoom
(
nickname
.
value
,
token
.
value
,
avatar
.
value
,
gameId
,
);
},
200
);
},
onGameStart
:
async
()
=>
{
showGameRule
.
value
=
false
await
mobileStageRef
.
value
?.
startCountdown
()
startGameView
()
showGameRule
.
value
=
false
;
await
mobileStageRef
.
value
?.
startCountdown
();
startGameView
();
},
onScoreSubmitted
:
(
is_save
,
data
)
=>
{
const
nextScore
=
Number
(
typeof
data
===
'object'
?
data
?.
score
:
NaN
)
const
nextRank
=
Number
(
typeof
data
===
'object'
?
data
?.
rank
:
data
)
const
remainingSeconds
=
Number
(
typeof
data
===
'object'
?
data
?.
remainingSeconds
:
NaN
)
const
nextScore
=
Number
(
typeof
data
===
"object"
?
data
?.
score
:
NaN
);
const
nextRank
=
Number
(
typeof
data
===
"object"
?
data
?.
rank
:
data
);
const
remainingSeconds
=
Number
(
typeof
data
===
"object"
?
data
?.
remainingSeconds
:
NaN
,
);
if
(
!
is_save
&&
Number
.
isFinite
(
nextScore
)
&&
nextScore
>=
0
)
{
tick
.
value
=
nextScore
tick
.
value
=
nextScore
;
}
if
(
!
is_save
&&
Number
.
isFinite
(
remainingSeconds
)
&&
remainingSeconds
>=
0
)
{
syncGameCountdown
(
remainingSeconds
)
if
(
!
is_save
&&
Number
.
isFinite
(
remainingSeconds
)
&&
remainingSeconds
>=
0
)
{
syncGameCountdown
(
remainingSeconds
);
}
if
(
Number
.
isFinite
(
nextRank
)
&&
nextRank
>
0
)
{
rank
.
value
=
nextRank
rank
.
value
=
nextRank
;
}
return
{
code
:
'CMYF'
,
score
:
tick
.
value
}
return
{
code
:
"CMYF"
,
score
:
tick
.
value
};
},
onRescoreSubmitted
:
(
_recount
)
=>
{
submitScore
(
true
)
submitScore
(
true
);
},
onRoomClosed
:
showLoadingView
,
onRoomBack
:
handleRoomBack
,
onGameRecover
:()
=>
{
debugger
}
})
_offSocketMessage
.
value
=
offSocketMessage
onGameRecover
:
()
=>
{
debugger
;
},
});
_offSocketMessage
.
value
=
offSocketMessage
;
}
onMounted
(()
=>
{
document
.
title
=
'互动游戏 - 策马迎福'
document
.
title
=
"互动游戏 - 策马迎福"
;
if
(
token
.
value
)
{
window
.
addEventListener
(
'beforeunload'
,
confirmRefresh
)
window
.
addEventListener
(
"beforeunload"
,
confirmRefresh
);
}
})
})
;
onBeforeUnmount
(()
=>
{
_offSocketMessage
.
value
?.()
stopGameCountdown
()
_offSocketMessage
.
value
?.();
stopGameCountdown
();
if
(
token
.
value
)
{
window
.
removeEventListener
(
'beforeunload'
,
confirmRefresh
)
window
.
removeEventListener
(
"beforeunload"
,
confirmRefresh
);
}
})
})
;
</
script
>
<
template
>
<MobileStage
v-if=
"token"
:showGameRule=
"showGameRule"
ref=
"mobileStageRef"
:background=
"stageBackground"
>
<MobileStage
v-if=
"token"
:showGameRule=
"showGameRule"
ref=
"mobileStageRef"
:background=
"stageBackground"
>
<template
#
gameRule
>
<div
class=
"rule-container"
>
<div
class=
"rule-txt-container"
>
...
...
@@ -263,23 +280,50 @@ onBeforeUnmount(() => {
</div>
</div>
</
template
>
<LoadingView
v-if=
"currentView === 'loading'"
:image-urls=
"imageUrls"
:user-join-status=
"userJoinStatus"
@
touchGameRule=
"showGameRuleHandler"
/>
<LoadingView
v-if=
"currentView === 'loading'"
:image-urls=
"imageUrls"
:user-join-status=
"userJoinStatus"
@
touchGameRule=
"showGameRuleHandler"
/>
<!-- <PlayingView v-else :image-urls="imageUrls" :tick="tick" :rank="rank" -->
<PlayingView
v-else-if=
"currentView === 'playing'"
:image-urls=
"imageUrls"
:tick=
"tick"
:rank=
"rank"
:currentHorse=
"currentHorse"
:currentYaoyiyao=
"currentYaoyiyao"
:countdown-interval=
"countdownInterval"
:is-div-desc-visible=
"isDivDescVisible"
@
touch=
"touchHandler"
/>
<ScoreView
v-else
:image-urls=
"imageUrls"
:tick=
"tick"
:level=
"rank"
@
replay=
"replayGame"
@
back=
"backToWaiting"
/>
<PlayingView
v-else-if=
"currentView === 'playing'"
:image-urls=
"imageUrls"
:tick=
"tick"
:rank=
"rank"
:currentHorse=
"currentHorse"
:currentYaoyiyao=
"currentYaoyiyao"
:countdown-interval=
"countdownInterval"
:is-div-desc-visible=
"isDivDescVisible"
@
touch=
"touchHandler"
/>
<ScoreView
v-else
:image-urls=
"imageUrls"
:tick=
"tick"
:level=
"rank"
@
replay=
"replayGame"
@
back=
"backToWaiting"
/>
</MobileStage>
<div
v-else
style=
"text-align: center; width: 100vw; height: 100vh; line-height: 30; font-size: 20px;"
>
<div
v-else
style=
"
text-align: center;
width: 100vw;
height: 100vh;
line-height: 30;
font-size: 20px;
"
>
请使用微信扫码进入游戏
</div>
</template>
<
style
scoped
>
.rule-container
{
.rule-txt-container
{
background
:
v-bind
(
'imageUrls.rule'
)
center
/
cover
no-repeat
;
background
:
v-bind
(
"imageUrls.rule"
)
center
/
cover
no-repeat
;
width
:
692px
;
height
:
797px
;
margin-top
:
-20%
;
...
...
@@ -298,14 +342,14 @@ onBeforeUnmount(() => {
font-size
:
26pt
;
padding
:
40px
;
margin-top
:
10px
;
color
:
#AA
0000
;
color
:
#aa
0000
;
}
}
.rule-close
{
width
:
57px
;
height
:
57px
;
background
:
v-bind
(
'imageUrls.close'
)
center
/
cover
no-repeat
;
background
:
v-bind
(
"imageUrls.close"
)
center
/
cover
no-repeat
;
transform
:
translateX
(
-50%
)
scale
(
1.5
);
position
:
absolute
;
top
:
-73px
;
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment