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
6d448527
Commit
6d448527
authored
Jun 04, 2026
by
董政锦
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: game4 修复马短暂脉冲加速;
parent
2499dbb9
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
140 additions
and
33 deletions
+140
-33
Game4.vue
frontend-large/src/pages/game4/Game4.vue
+3
-4
Rank1View.vue
frontend-large/src/pages/game4/views/Rank1View.vue
+117
-21
Rank2View.vue
frontend-large/src/pages/game4/views/Rank2View.vue
+17
-5
index.ts
frontend-large/src/router/index.ts
+3
-3
No files found.
frontend-large/src/pages/game4/Game4.vue
View file @
6d448527
...
@@ -66,12 +66,12 @@ offHorseBoost = onSocketMessage((evt, payload) => {
...
@@ -66,12 +66,12 @@ offHorseBoost = onSocketMessage((evt, payload) => {
data
.
forEach
((
item
:
any
)
=>
{
data
.
forEach
((
item
:
any
)
=>
{
const
id
=
item
.
userid
||
item
.
wechat_id
||
""
;
const
id
=
item
.
userid
||
item
.
wechat_id
||
""
;
if
(
!
id
)
return
;
if
(
!
id
)
return
;
// 尝试多种字段名: boost / acceleration / speed / progress
// 尝试多种字段名:
score /
boost / acceleration / speed / progress
const
raw
=
item
.
boost
??
item
.
acceleration
??
item
.
speed
??
item
.
progress
;
const
raw
=
item
.
score
??
item
.
boost
??
item
.
acceleration
??
item
.
speed
??
item
.
progress
;
if
(
raw
===
undefined
||
raw
===
null
)
return
;
if
(
raw
===
undefined
||
raw
===
null
)
return
;
const
val
=
Number
(
raw
);
const
val
=
Number
(
raw
);
if
(
isNaN
(
val
))
return
;
if
(
isNaN
(
val
))
return
;
//
boost 范围 0~N,0 表示无加速(基准60秒跑完),>0
越快
//
score/boost 范围 0~N,0 表示无加速,越大
越快
boosts
[
id
]
=
Math
.
max
(
0
,
val
);
boosts
[
id
]
=
Math
.
max
(
0
,
val
);
hasBoost
=
true
;
hasBoost
=
true
;
});
});
...
@@ -145,7 +145,6 @@ function handleRoomState(data: any) {
...
@@ -145,7 +145,6 @@ function handleRoomState(data: any) {
const
{
startGame
,
createRoom
,
requestRoomState
}
=
useAdminGameSocket
({
const
{
startGame
,
createRoom
,
requestRoomState
}
=
useAdminGameSocket
({
gameId
:
"game4"
,
gameId
:
"game4"
,
// loginRedirectPath: '/game4',
// loginRedirectPath: '/game4',
onGameStartRejected
:
gotoLoading
,
onRoomState
:
handleRoomState
,
onRoomState
:
handleRoomState
,
onRoomJoin
:
(
data
)
=>
{
onRoomJoin
:
(
data
)
=>
{
renderPlayers
(
data
);
renderPlayers
(
data
);
...
...
frontend-large/src/pages/game4/views/Rank1View.vue
View file @
6d448527
...
@@ -39,10 +39,51 @@ const imageUrls = {
...
@@ -39,10 +39,51 @@ const imageUrls = {
avatarAnimation
:
cssAssetUrl
(
"avatar_animation.png"
),
avatarAnimation
:
cssAssetUrl
(
"avatar_animation.png"
),
};
};
// ========== 速度基准:
60秒从屏幕左侧跑到右侧
==========
// ========== 速度基准:
90秒从屏幕左侧跑到右侧(调低初始速度)
==========
const
TOTAL_DISTANCE_PX
=
2160
;
// -120px ~ 2040px
const
TOTAL_DISTANCE_PX
=
2160
;
// -120px ~ 2040px
const
BASE_DURATION_SEC
=
60
;
const
BASE_DURATION_SEC
=
90
;
const
BASE_SPEED_PERCENT_PER_SEC
=
100
/
BASE_DURATION_SEC
;
// ≈ 1.667%/s 基准速度
const
BASE_SPEED_PERCENT_PER_SEC
=
100
/
BASE_DURATION_SEC
;
// ≈ 1.111%/s 基准速度
// ========== 瞬时加速脉冲机制 ==========
// H5 每次摇一摇发送累计积分,PC 端检测 score 增长 → 触发短暂加速脉冲 → 迅速衰减
const
BOOST_PULSE
=
0.8
;
// 每次操作的加速幅度(+80% 额外速度)
const
BOOST_DECAY
=
0.97
;
// 每帧衰减系数(60fps 下约 1s 衰减到 16%,约 2s 衰减归零)
/** 瞬时加速值: { [userid]: currentBoost },每帧衰减 */
const
transientBoosts
=
ref
<
Record
<
string
,
number
>>
({});
/** 上一轮各玩家累计积分,用于检测增长 */
const
prevScores
:
Record
<
string
,
number
>
=
{};
/** 检测 rankList 中哪些玩家 score 增长了 → 触发瞬时加速脉冲 */
const
detectScoreChanges
=
(
players
:
Player
[])
=>
{
if
(
!
players
||
players
.
length
===
0
)
return
;
let
hasNewBoost
=
false
;
const
updated
=
{
...
transientBoosts
.
value
};
players
.
forEach
((
p
)
=>
{
const
id
=
p
.
userid
||
p
.
wechat_id
||
""
;
if
(
!
id
||
id
.
startsWith
(
"empty-"
))
return
;
const
newScore
=
Number
(
p
.
score
??
0
);
const
prev
=
prevScores
[
id
]
??
0
;
if
(
newScore
>
prev
)
{
// 积分增长 = 玩家又摇了一次 → 触发瞬时加速脉冲
updated
[
id
]
=
BOOST_PULSE
;
hasNewBoost
=
true
;
}
prevScores
[
id
]
=
newScore
;
});
if
(
hasNewBoost
)
{
transientBoosts
.
value
=
updated
;
console
.
log
(
"[Rank1View] 瞬时加速触发:"
,
JSON
.
parse
(
JSON
.
stringify
(
transientBoosts
.
value
)),
);
}
};
interface
Horse
{
interface
Horse
{
id
:
string
;
id
:
string
;
...
@@ -66,6 +107,8 @@ const horses = ref<Horse[]>([]);
...
@@ -66,6 +107,8 @@ const horses = ref<Horse[]>([]);
const
isGameRunning
=
ref
(
false
);
const
isGameRunning
=
ref
(
false
);
const
showResult
=
ref
(
false
);
const
showResult
=
ref
(
false
);
const
finalRankList
=
ref
<
RankItem
[]
>
([]);
const
finalRankList
=
ref
<
RankItem
[]
>
([]);
/** 倒计时结束后才为 true,控制背景动画和马匹渲染 */
const
gameStarted
=
ref
(
false
);
const
LANE_POSITIONS
=
[
42
,
160
,
270
,
380
,
490
];
const
LANE_POSITIONS
=
[
42
,
160
,
270
,
380
,
490
];
...
@@ -109,6 +152,8 @@ watch(
...
@@ -109,6 +152,8 @@ watch(
(
players
)
=>
{
(
players
)
=>
{
if
(
players
&&
players
.
length
>
0
)
{
if
(
players
&&
players
.
length
>
0
)
{
updateHorses
(
players
);
updateHorses
(
players
);
// 检测积分增长 → 触发瞬时加速脉冲
detectScoreChanges
(
players
);
}
}
},
},
{
immediate
:
true
,
deep
:
true
},
{
immediate
:
true
,
deep
:
true
},
...
@@ -119,24 +164,31 @@ const horseTranslateX = (xPercent: number) => {
...
@@ -119,24 +164,31 @@ const horseTranslateX = (xPercent: number) => {
return
-
120
+
(
xPercent
/
100
)
*
TOTAL_DISTANCE_PX
;
return
-
120
+
(
xPercent
/
100
)
*
TOTAL_DISTANCE_PX
;
};
};
// ========== 接收 H5 传来的加速度 ==========
// ========== 接收 H5 传来的 acceleration / boost 数据 ==========
// 注意:rankList 的 score 增长检测是主通道,此处作为兜底
watch
(
watch
(
()
=>
props
.
horseBoosts
,
()
=>
props
.
horseBoosts
,
(
boosts
)
=>
{
(
boosts
)
=>
{
if
(
!
boosts
||
Object
.
keys
(
boosts
).
length
===
0
)
return
;
if
(
!
boosts
||
Object
.
keys
(
boosts
).
length
===
0
)
return
;
let
hasNew
=
false
;
const
updated
=
{
...
transientBoosts
.
value
};
Object
.
keys
(
boosts
).
forEach
((
id
)
=>
{
const
val
=
Number
(
boosts
[
id
]
??
0
);
if
(
val
>
0
)
{
// 每次收到有效数据都刷新脉冲(叠加摇一摇的连续加速感)
updated
[
id
]
=
BOOST_PULSE
;
hasNew
=
true
;
}
});
if
(
hasNew
)
{
transientBoosts
.
value
=
updated
;
console
.
log
(
console
.
log
(
`[Rank1View] 收到加速度数据
(
${
new
Date
().
toLocaleTimeString
()}
):`
,
`[Rank1View] horseBoosts 触发瞬时加速
(
${
new
Date
().
toLocaleTimeString
()}
):`
,
JSON
.
parse
(
JSON
.
stringify
(
boosts
)),
JSON
.
parse
(
JSON
.
stringify
(
transientBoosts
.
value
)),
);
);
horses
.
value
.
forEach
((
horse
)
=>
{
const
boost
=
boosts
[
horse
.
id
];
if
(
boost
!==
undefined
)
{
horse
.
boost
=
boost
;
console
.
log
(
` 🐴
${
horse
.
nickname
}
(
${
horse
.
id
}
): boost=
${
boost
}
`
);
}
}
});
},
},
{
deep
:
true
},
{
deep
:
true
},
);
);
...
@@ -152,18 +204,34 @@ const startGameLoop = () => {
...
@@ -152,18 +204,34 @@ const startGameLoop = () => {
if
(
!
isGameRunning
.
value
)
return
;
if
(
!
isGameRunning
.
value
)
return
;
let
allFinished
=
true
;
let
allFinished
=
true
;
const
newTransientBoosts
:
Record
<
string
,
number
>
=
{};
horses
.
value
.
forEach
((
horse
)
=>
{
horses
.
value
.
forEach
((
horse
)
=>
{
if
(
horse
.
xPercent
>=
100
)
return
;
if
(
horse
.
xPercent
>=
100
)
return
;
// 实际速度 = 基准速度 × (1 + boost)
// 瞬时加速值(来自积分增长脉冲,每帧衰减中)
const
boost
=
transientBoosts
.
value
[
horse
.
id
]
||
0
;
horse
.
boost
=
boost
;
// 实际速度 = 基准速度 × (1 + 瞬时boost)
horse
.
xPercent
=
Math
.
min
(
horse
.
xPercent
=
Math
.
min
(
100
,
100
,
horse
.
xPercent
+
BASE_SPEED_PERCENT_PER_SEC
*
(
1
+
horse
.
boost
)
*
TICK_SEC
,
horse
.
xPercent
+
BASE_SPEED_PERCENT_PER_SEC
*
(
1
+
boost
)
*
TICK_SEC
,
);
);
// 衰减瞬时加速:每帧 × 衰减系数
const
decayed
=
boost
*
BOOST_DECAY
;
if
(
decayed
>
0.005
)
{
newTransientBoosts
[
horse
.
id
]
=
decayed
;
}
if
(
horse
.
xPercent
<
100
)
allFinished
=
false
;
if
(
horse
.
xPercent
<
100
)
allFinished
=
false
;
});
});
// 更新衰减后的加速值
transientBoosts
.
value
=
newTransientBoosts
;
if
(
allFinished
&&
horses
.
value
.
length
>
0
)
{
if
(
allFinished
&&
horses
.
value
.
length
>
0
)
{
stopGame
();
stopGame
();
}
}
...
@@ -206,13 +274,13 @@ const computeFinalRank = (): RankItem[] => {
...
@@ -206,13 +274,13 @@ const computeFinalRank = (): RankItem[] => {
if
(
valid
.
length
>
0
)
{
if
(
valid
.
length
>
0
)
{
// 优先用 WebSocket 的 score 排序
// 优先用 WebSocket 的 score 排序
const
sorted
=
[...
valid
].
sort
((
a
,
b
)
=>
(
b
.
score
??
0
)
-
(
a
.
score
??
0
));
const
sorted
=
[...
valid
].
sort
((
a
,
b
)
=>
Number
(
b
.
score
??
0
)
-
Number
(
a
.
score
??
0
));
return
sorted
.
map
((
p
,
i
)
=>
({
return
sorted
.
map
((
p
,
i
)
=>
({
rank
:
i
+
1
,
rank
:
i
+
1
,
userid
:
p
.
userid
||
p
.
wechat_id
||
`unknown-
${
i
}
`
,
userid
:
p
.
userid
||
p
.
wechat_id
||
`unknown-
${
i
}
`
,
nickname
:
p
.
nickname
,
nickname
:
p
.
nickname
,
avatar
:
p
.
avatar
||
""
,
avatar
:
p
.
avatar
||
""
,
score
:
p
.
score
??
0
,
score
:
Number
(
p
.
score
??
0
)
,
}));
}));
}
}
...
@@ -255,15 +323,39 @@ const startIntro = async () => {
...
@@ -255,15 +323,39 @@ const startIntro = async () => {
if
(
$is_run_local
())
{
if
(
$is_run_local
())
{
mockHorses
();
mockHorses
();
}
}
isGameRunning
.
value
=
true
;
showResult
.
value
=
false
;
showResult
.
value
=
false
;
finalRankList
.
value
=
[];
finalRankList
.
value
=
[];
gameStarted
.
value
=
false
;
isGameRunning
.
value
=
false
;
// 重置瞬时加速和积分追踪
transientBoosts
.
value
=
{};
Object
.
keys
(
prevScores
).
forEach
((
k
)
=>
delete
prevScores
[
k
]);
// Phase 1: 3 秒开场倒计时(背景动画暂停、马匹不显示)
await
designStage
.
value
?.
startCountdown
(
3
,
0
);
// Phase 2: 倒计时结束 → 游戏正式开始
gameStarted
.
value
=
true
;
isGameRunning
.
value
=
true
;
startGameLoop
();
startGameLoop
();
if
(
$is_run_local
())
{
if
(
$is_run_local
())
{
startMockBoost
();
startMockBoost
();
}
}
// DesignStage 倒计时: 开场3秒 + 游戏60秒
await
designStage
.
value
?.
startCountdown
(
3
,
60
);
// Phase 3: 等待游戏结束(最多 60 秒 或 所有马跑完)
const
maxMs
=
60000
;
const
gameStart
=
Date
.
now
();
await
new
Promise
<
void
>
((
resolve
)
=>
{
const
check
=
setInterval
(()
=>
{
const
allDone
=
horses
.
value
.
length
>
0
&&
horses
.
value
.
every
((
h
)
=>
h
.
xPercent
>=
100
);
const
timeUp
=
Date
.
now
()
-
gameStart
>=
maxMs
;
if
(
allDone
||
timeUp
||
!
isGameRunning
.
value
)
{
clearInterval
(
check
);
resolve
();
}
},
200
);
});
stopGame
();
stopGame
();
};
};
...
@@ -301,6 +393,7 @@ const podiumPlayers = (): RankItem[] => {
...
@@ -301,6 +393,7 @@ const podiumPlayers = (): RankItem[] => {
onMounted
(()
=>
{
onMounted
(()
=>
{
if
(
$is_run_local
())
{
if
(
$is_run_local
())
{
mockHorses
();
mockHorses
();
gameStarted
.
value
=
true
;
isGameRunning
.
value
=
true
;
isGameRunning
.
value
=
true
;
startGameLoop
();
startGameLoop
();
startMockBoost
();
startMockBoost
();
...
@@ -318,7 +411,7 @@ defineExpose({
...
@@ -318,7 +411,7 @@ defineExpose({
<
template
>
<
template
>
<DesignStage
ref=
"designStage"
>
<DesignStage
ref=
"designStage"
>
<div
class=
"screen-container"
:class=
"
{ paused: !
isGameRunning
}">
<div
class=
"screen-container"
:class=
"
{ paused: !
gameStarted
}">
<div
class=
"img-logo"
></div>
<div
class=
"img-logo"
></div>
<div
class=
"img-title-bg"
></div>
<div
class=
"img-title-bg"
></div>
<div
class=
"img-big-brum"
></div>
<div
class=
"img-big-brum"
></div>
...
@@ -330,6 +423,8 @@ defineExpose({
...
@@ -330,6 +423,8 @@ defineExpose({
<div
class=
"bg-bottom-inner"
></div>
<div
class=
"bg-bottom-inner"
></div>
<div
class=
"bg-bottom-inner"
></div>
<div
class=
"bg-bottom-inner"
></div>
</div>
</div>
<!-- 倒计时结束后才显示马匹 -->
<template
v-if=
"gameStarted"
>
<div
<div
v-for=
"horse in horses"
v-for=
"horse in horses"
:key=
"horse.id"
:key=
"horse.id"
...
@@ -345,6 +440,7 @@ defineExpose({
...
@@ -345,6 +440,7 @@ defineExpose({
</div>
</div>
<div
class=
"horse-avatar"
:style=
"horseAvatarStyle(horse)"
></div>
<div
class=
"horse-avatar"
:style=
"horseAvatarStyle(horse)"
></div>
</div>
</div>
</
template
>
<div
v-if=
"showResult"
class=
"result-overlay"
>
<div
v-if=
"showResult"
class=
"result-overlay"
>
<div
class=
"result-mask"
></div>
<div
class=
"result-mask"
></div>
...
...
frontend-large/src/pages/game4/views/Rank2View.vue
View file @
6d448527
...
@@ -14,7 +14,7 @@ const emit = defineEmits<{
...
@@ -14,7 +14,7 @@ const emit = defineEmits<{
const
FINAL_RANK_LIMIT
=
10
;
const
FINAL_RANK_LIMIT
=
10
;
const
imageUrls
=
{
const
imageUrls
=
{
bg
:
cssAssetUrl
(
"game4/bg.
pn
g"
),
bg
:
cssAssetUrl
(
"game4/bg.
sv
g"
),
logo
:
cssAssetUrl
(
"game1/logo.png"
),
logo
:
cssAssetUrl
(
"game1/logo.png"
),
title
:
cssAssetUrl
(
"game1/title3.png"
),
title
:
cssAssetUrl
(
"game1/title3.png"
),
title2
:
cssAssetUrl
(
"game1/title4.png"
),
title2
:
cssAssetUrl
(
"game1/title4.png"
),
...
@@ -68,21 +68,30 @@ watch(
...
@@ -68,21 +68,30 @@ watch(
<div
class=
"podium"
>
<div
class=
"podium"
>
<div
class=
"rank-no1"
>
<div
class=
"rank-no1"
>
<div
class=
"gu-burst"
></div>
<div
class=
"gu-burst"
></div>
<div
class=
"avatar"
></div>
<div
class=
"avatar"
:style=
"
{ backgroundImage: `url(${list[0]?.avatar || imageUrls.avatar})` }"
>
</div>
<div
class=
"nickname"
>
{{
list
[
0
]?.
nickname
}}
</div>
<div
class=
"nickname"
>
{{
list
[
0
]?.
nickname
}}
</div>
<div
class=
"guan"
></div>
<div
class=
"guan"
></div>
<div
class=
"pai"
></div>
<div
class=
"pai"
></div>
</div>
</div>
<div
class=
"rank-no2"
>
<div
class=
"rank-no2"
>
<div
class=
"gu-burst"
></div>
<div
class=
"gu-burst"
></div>
<div
class=
"avatar"
></div>
<div
class=
"avatar"
:style=
"
{ backgroundImage: `url(${list[1]?.avatar || imageUrls.avatar})` }"
>
</div>
<div
class=
"nickname"
>
{{
list
[
1
]?.
nickname
}}
</div>
<div
class=
"nickname"
>
{{
list
[
1
]?.
nickname
}}
</div>
<div
class=
"guan"
></div>
<div
class=
"guan"
></div>
<div
class=
"pai"
></div>
<div
class=
"pai"
></div>
</div>
</div>
<div
class=
"rank-no3"
>
<div
class=
"rank-no3"
>
<div
class=
"gu-burst"
></div>
<div
class=
"gu-burst"
></div>
<div
class=
"avatar"
></div>
<div
class=
"avatar"
:style=
"
{ backgroundImage: `url(${list[2]?.avatar || imageUrls.avatar})` }"
>
</div>
<div
class=
"nickname"
>
{{
list
[
2
]?.
nickname
}}
</div>
<div
class=
"nickname"
>
{{
list
[
2
]?.
nickname
}}
</div>
<div
class=
"guan"
></div>
<div
class=
"guan"
></div>
<div
class=
"pai"
></div>
<div
class=
"pai"
></div>
...
@@ -97,7 +106,10 @@ watch(
...
@@ -97,7 +106,10 @@ watch(
<div
class=
"no-icon"
>
{{
index
+
4
}}
</div>
<div
class=
"no-icon"
>
{{
index
+
4
}}
</div>
<div>
<div>
<div
class=
"img-avatar-container"
>
<div
class=
"img-avatar-container"
>
<div
class=
"img-avatar"
></div>
<div
class=
"img-avatar"
:style=
"
{ backgroundImage: `url(${item.avatar || imageUrls.avatar})` }"
>
</div>
</div>
</div>
<div
class=
"txt-nickname"
>
{{
item
.
nickname
}}
</div>
<div
class=
"txt-nickname"
>
{{
item
.
nickname
}}
</div>
</div>
</div>
...
...
frontend-large/src/router/index.ts
View file @
6d448527
...
@@ -2,8 +2,8 @@ import { createRouter, createWebHashHistory } from 'vue-router'
...
@@ -2,8 +2,8 @@ import { createRouter, createWebHashHistory } from 'vue-router'
import
{
$read
}
from
'@/commons/utils'
import
{
$read
}
from
'@/commons/utils'
import
Login
from
'@/pages/Login.vue'
import
Login
from
'@/pages/Login.vue'
import
Game1
from
'@/pages/game1/Game.vue'
import
Game1
from
'@/pages/game1/Game.vue'
import
Game6
from
'@/pages/game6/Game.vue'
import
Game4
from
'@/pages/game4/Game4.vue'
import
Game4
from
'@/pages/game4/Game4.vue'
import
Game6
from
'@/pages/game6/Game.vue'
const
router
=
createRouter
({
const
router
=
createRouter
({
history
:
createWebHashHistory
(),
history
:
createWebHashHistory
(),
...
@@ -28,7 +28,7 @@ const router = createRouter({
...
@@ -28,7 +28,7 @@ const router = createRouter({
{
{
path
:
'/game4'
,
path
:
'/game4'
,
name
:
'Game4'
,
name
:
'Game4'
,
component
:
Game
1
,
component
:
Game
4
,
meta
:
{
meta
:
{
requiresAuth
:
true
,
requiresAuth
:
true
,
},
},
...
@@ -36,7 +36,7 @@ const router = createRouter({
...
@@ -36,7 +36,7 @@ const router = createRouter({
{
{
path
:
'/game6'
,
path
:
'/game6'
,
name
:
'Game6'
,
name
:
'Game6'
,
component
:
Game
1
,
component
:
Game
6
,
meta
:
{
meta
:
{
requiresAuth
:
true
,
requiresAuth
:
true
,
},
},
...
...
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