Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
P
paopao
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
cocktail-party
paopao
Commits
0d709a43
提交
0d709a43
authored
3月 21, 2026
作者:
lidongxu
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
特效很棒还差点
上级
8eb3a4a9
隐藏空白字符变更
内嵌
并排
正在显示
3 个修改的文件
包含
143 行增加
和
74 行删除
+143
-74
main.js
big-screen/src/main.js
+3
-2
explosion.js
big-screen/src/renderer/explosion.js
+139
-71
minigame-1
minigame-1
+1
-1
没有找到文件。
big-screen/src/main.js
浏览文件 @
0d709a43
...
@@ -9,7 +9,7 @@ import { drawBubbleGrid } from './renderer/bubbleGrid.js'
...
@@ -9,7 +9,7 @@ import { drawBubbleGrid } from './renderer/bubbleGrid.js'
import
{
drawBubble3D
,
BUBBLE_RADIUS
}
from
'./renderer/bubble.js'
import
{
drawBubble3D
,
BUBBLE_RADIUS
}
from
'./renderer/bubble.js'
import
{
drawShooter
}
from
'./renderer/shooter.js'
import
{
drawShooter
}
from
'./renderer/shooter.js'
import
{
drawGameInfo
,
drawTeamResultOverlay
}
from
'./renderer/gameinfo.js'
import
{
drawGameInfo
,
drawTeamResultOverlay
}
from
'./renderer/gameinfo.js'
import
{
updateAndDrawExplosions
,
appendExplosionsFromState
,
Explosion
}
from
'./renderer/explosion.js'
import
{
updateAndDrawExplosions
,
appendExplosionsFromState
,
Explosion
,
setExplosionQuality
}
from
'./renderer/explosion.js'
import
{
drawIdleScreen
}
from
'./renderer/idleScreen.js'
import
{
drawIdleScreen
}
from
'./renderer/idleScreen.js'
import
{
SCREEN_WIDTH
,
SCREEN_HEIGHT
}
from
'./constants.js'
import
{
SCREEN_WIDTH
,
SCREEN_HEIGHT
}
from
'./constants.js'
...
@@ -195,10 +195,11 @@ function loop() {
...
@@ -195,10 +195,11 @@ function loop() {
const
teamBStates
=
states
.
filter
(
s
=>
getPlayerTeam
(
s
.
playerId
??
1
)
===
'B'
)
const
teamBStates
=
states
.
filter
(
s
=>
getPlayerTeam
(
s
.
playerId
??
1
)
===
'B'
)
const
totalSlots
=
Math
.
max
(
teamAStates
.
length
+
teamBStates
.
length
,
1
)
const
totalSlots
=
Math
.
max
(
teamAStates
.
length
+
teamBStates
.
length
,
1
)
// 人数变化时重新计算缩放
// 人数变化时重新计算缩放
和爆炸质量
if
(
totalSlots
!==
_lastPlayerCount
)
{
if
(
totalSlots
!==
_lastPlayerCount
)
{
_lastPlayerCount
=
totalSlots
_lastPlayerCount
=
totalSlots
applyScaler
(
totalSlots
)
applyScaler
(
totalSlots
)
setExplosionQuality
(
totalSlots
)
// 根据人数调整爆炸效果质量
// 清理消失玩家的爆炸列表
// 清理消失玩家的爆炸列表
for
(
const
pid
of
playerExplosions
.
keys
())
{
for
(
const
pid
of
playerExplosions
.
keys
())
{
if
(
!
states
.
find
(
s
=>
(
s
.
playerId
??
1
)
===
pid
))
{
if
(
!
states
.
find
(
s
=>
(
s
.
playerId
??
1
)
===
pid
))
{
...
...
big-screen/src/renderer/explosion.js
浏览文件 @
0d709a43
/**
/**
* 爆炸粒子特效 —— 与 minigame-1/js/effects/explosion.js 1:1 复刻
* 爆炸粒子特效 —— 与 minigame-1/js/effects/explosion.js 1:1 复刻
* 去除 wx 依赖,使用浏览器 Canvas 2D API
* 去除 wx 依赖,使用浏览器 Canvas 2D API
* 优化:批量渲染 + 动态质量调整,支持4人同屏
*/
*/
import
{
BUBBLE_RADIUS
,
BUBBLE_COLORS
}
from
'./bubble.js'
import
{
BUBBLE_RADIUS
,
BUBBLE_COLORS
}
from
'./bubble.js'
// ─── 全局配置:根据同屏玩家数动态调整质量 ─────────────────────────────────────
const
QUALITY_CONFIG
=
{
// 单人/双人 - 高质量
low
:
{
circleCount
:
12
,
glintCount
:
5
,
sparkCount
:
5
,
ringCount
:
2
,
maxLifeBase
:
25
},
// 三人 - 中等质量
medium
:
{
circleCount
:
8
,
glintCount
:
3
,
sparkCount
:
3
,
ringCount
:
2
,
maxLifeBase
:
20
},
// 四人 - 性能优先
high
:
{
circleCount
:
6
,
glintCount
:
2
,
sparkCount
:
2
,
ringCount
:
1
,
maxLifeBase
:
18
}
}
let
currentQuality
=
'medium'
/**
* 设置爆炸质量等级(根据同屏玩家数调用)
* @param {number} playerCount 当前游戏人数 1-4
*/
export
function
setExplosionQuality
(
playerCount
)
{
if
(
playerCount
<=
2
)
currentQuality
=
'low'
else
if
(
playerCount
===
3
)
currentQuality
=
'medium'
else
currentQuality
=
'high'
}
// ─── 圆形粒子 ─────────────────────────────────────────────────────────────────
// ─── 圆形粒子 ─────────────────────────────────────────────────────────────────
class
CircleParticle
{
class
CircleParticle
{
...
@@ -22,7 +63,7 @@ class CircleParticle {
...
@@ -22,7 +63,7 @@ class CircleParticle {
update
()
{
update
()
{
this
.
vx
*=
0.92
this
.
vx
*=
0.92
this
.
vy
=
this
.
vy
*
0.92
+
0.2
// 摩擦 + 重力
this
.
vy
=
this
.
vy
*
0.92
+
0.2
this
.
x
+=
this
.
vx
this
.
x
+=
this
.
vx
this
.
y
+=
this
.
vy
this
.
y
+=
this
.
vy
this
.
life
++
this
.
life
++
...
@@ -30,17 +71,6 @@ class CircleParticle {
...
@@ -30,17 +71,6 @@ class CircleParticle {
this
.
radius
=
Math
.
max
(
0.5
,
this
.
radius
*
0.97
)
this
.
radius
=
Math
.
max
(
0.5
,
this
.
radius
*
0.97
)
if
(
this
.
life
>=
this
.
maxLife
)
this
.
alive
=
false
if
(
this
.
life
>=
this
.
maxLife
)
this
.
alive
=
false
}
}
render
(
ctx
)
{
if
(
!
this
.
alive
||
this
.
alpha
<=
0
)
return
ctx
.
save
()
ctx
.
globalAlpha
=
this
.
alpha
ctx
.
beginPath
()
ctx
.
arc
(
this
.
x
,
this
.
y
,
this
.
radius
,
0
,
Math
.
PI
*
2
)
ctx
.
fillStyle
=
this
.
color
ctx
.
fill
()
ctx
.
restore
()
}
}
}
// ─── 火花线条粒子 ─────────────────────────────────────────────────────────────
// ─── 火花线条粒子 ─────────────────────────────────────────────────────────────
...
@@ -58,6 +88,7 @@ class SparkParticle {
...
@@ -58,6 +88,7 @@ class SparkParticle {
this
.
life
=
0
this
.
life
=
0
this
.
maxLife
=
18
+
Math
.
floor
(
Math
.
random
()
*
14
)
this
.
maxLife
=
18
+
Math
.
floor
(
Math
.
random
()
*
14
)
this
.
alive
=
true
this
.
alive
=
true
this
.
isSpark
=
true
}
}
update
()
{
update
()
{
...
@@ -71,20 +102,6 @@ class SparkParticle {
...
@@ -71,20 +102,6 @@ class SparkParticle {
this
.
alpha
=
Math
.
max
(
0
,
1
-
this
.
life
/
this
.
maxLife
)
this
.
alpha
=
Math
.
max
(
0
,
1
-
this
.
life
/
this
.
maxLife
)
if
(
this
.
life
>=
this
.
maxLife
)
this
.
alive
=
false
if
(
this
.
life
>=
this
.
maxLife
)
this
.
alive
=
false
}
}
render
(
ctx
)
{
if
(
!
this
.
alive
||
this
.
alpha
<=
0
)
return
ctx
.
save
()
ctx
.
globalAlpha
=
this
.
alpha
ctx
.
strokeStyle
=
this
.
color
ctx
.
lineWidth
=
1.8
ctx
.
lineCap
=
'round'
ctx
.
beginPath
()
ctx
.
moveTo
(
this
.
x
,
this
.
y
)
ctx
.
lineTo
(
this
.
prevX
,
this
.
prevY
)
ctx
.
stroke
()
ctx
.
restore
()
}
}
}
// ─── 冲击波圆环 ───────────────────────────────────────────────────────────────
// ─── 冲击波圆环 ───────────────────────────────────────────────────────────────
...
@@ -96,32 +113,20 @@ class ShockRing {
...
@@ -96,32 +113,20 @@ class ShockRing {
this
.
color
=
colorHex
this
.
color
=
colorHex
this
.
r
=
BUBBLE_RADIUS
*
0.2
this
.
r
=
BUBBLE_RADIUS
*
0.2
this
.
maxR
=
BUBBLE_RADIUS
*
2.4
this
.
maxR
=
BUBBLE_RADIUS
*
2.4
this
.
life
=
-
delay
// 负值表示延迟未开始
this
.
life
=
-
delay
this
.
maxLife
=
1
4
this
.
maxLife
=
2
4
this
.
alpha
=
0
this
.
alpha
=
0
this
.
alive
=
true
this
.
alive
=
true
}
}
update
()
{
update
()
{
this
.
life
++
this
.
life
++
if
(
this
.
life
<=
0
)
return
// 等待延迟
if
(
this
.
life
<=
0
)
return
const
t
=
this
.
life
/
this
.
maxLife
const
t
=
this
.
life
/
this
.
maxLife
this
.
r
=
BUBBLE_RADIUS
*
0.2
+
(
this
.
maxR
-
BUBBLE_RADIUS
*
0.2
)
*
t
this
.
r
=
BUBBLE_RADIUS
*
0.2
+
(
this
.
maxR
-
BUBBLE_RADIUS
*
0.2
)
*
t
this
.
alpha
=
0.9
*
(
1
-
t
)
this
.
alpha
=
0.9
*
(
1
-
t
)
if
(
this
.
life
>=
this
.
maxLife
)
this
.
alive
=
false
if
(
this
.
life
>=
this
.
maxLife
)
this
.
alive
=
false
}
}
render
(
ctx
)
{
if
(
!
this
.
alive
||
this
.
alpha
<=
0
||
this
.
life
<=
0
)
return
ctx
.
save
()
ctx
.
globalAlpha
=
this
.
alpha
ctx
.
beginPath
()
ctx
.
arc
(
this
.
x
,
this
.
y
,
this
.
r
,
0
,
Math
.
PI
*
2
)
ctx
.
strokeStyle
=
this
.
color
ctx
.
lineWidth
=
2.5
ctx
.
stroke
()
ctx
.
restore
()
}
}
}
// ─── 单颗泡泡爆炸 ─────────────────────────────────────────────────────────────
// ─── 单颗泡泡爆炸 ─────────────────────────────────────────────────────────────
...
@@ -138,37 +143,64 @@ export class Explosion {
...
@@ -138,37 +143,64 @@ export class Explosion {
this
.
particles
=
[]
this
.
particles
=
[]
this
.
rings
=
[]
this
.
rings
=
[]
// 支持直接传 hex 字符串(大屏从 state.explosions[].colorHex 拿到的)
const
colorHex
=
typeof
color
===
'string'
&&
color
.
startsWith
(
'#'
)
const
colorHex
=
typeof
color
===
'string'
&&
color
.
startsWith
(
'#'
)
?
color
?
color
:
(
BUBBLE_COLORS
[
color
]
||
'#ffffff'
)
:
(
BUBBLE_COLORS
[
color
]
||
'#ffffff'
)
const
R
=
BUBBLE_RADIUS
const
R
=
BUBBLE_RADIUS
const
cfg
=
QUALITY_CONFIG
[
currentQuality
]
// 闪光帧数(缩短,一闪而过)
this
.
flashLife
=
isFloating
?
4
:
10
this
.
flashLife
=
isFloating
?
2
:
4
this
.
_x
=
x
this
.
_x
=
x
this
.
_y
=
y
this
.
_y
=
y
this
.
_colorHex
=
colorHex
this
.
_colorHex
=
colorHex
// ── 圆形粒子
(大幅减少数量,缩短寿命)
// ── 圆形粒子
const
circleCount
=
isFloating
?
3
:
5
const
circleCount
=
isFloating
?
Math
.
floor
(
cfg
.
circleCount
*
0.6
)
:
cfg
.
circleCount
for
(
let
i
=
0
;
i
<
circleCount
;
i
++
)
{
for
(
let
i
=
0
;
i
<
circleCount
;
i
++
)
{
const
angle
=
(
i
/
circleCount
)
*
Math
.
PI
*
2
+
(
Math
.
random
()
-
0.5
)
*
0.8
const
angle
=
(
i
/
circleCount
)
*
Math
.
PI
*
2
+
(
Math
.
random
()
-
0.5
)
*
0.8
const
speed
=
isFloating
const
speed
=
isFloating
?
1.5
+
Math
.
random
()
*
2
?
(
1.5
+
Math
.
random
()
*
3
)
:
2.5
+
Math
.
random
()
*
3.5
:
(
2.5
+
Math
.
random
()
*
5
)
const
vx
=
Math
.
cos
(
angle
)
*
speed
const
vx
=
Math
.
cos
(
angle
)
*
speed
const
vy
=
isFloating
const
vy
=
isFloating
?
Math
.
sin
(
angle
)
*
speed
*
0.4
+
1.5
?
(
Math
.
sin
(
angle
)
*
speed
*
0.4
+
1.5
)
:
Math
.
sin
(
angle
)
*
speed
:
Math
.
sin
(
angle
)
*
speed
const
radius
=
R
*
(
isFloating
?
0.
15
:
0.14
+
Math
.
random
()
*
0.1
8
)
const
radius
=
R
*
(
isFloating
?
0.
2
:
0.18
+
Math
.
random
()
*
0.2
8
)
const
maxLife
=
10
+
Math
.
floor
(
Math
.
random
()
*
8
)
// 10~18帧,约0.17~0.3s
const
maxLife
=
cfg
.
maxLifeBase
+
Math
.
floor
(
Math
.
random
()
*
20
)
this
.
particles
.
push
(
new
CircleParticle
(
x
,
y
,
colorHex
,
vx
,
vy
,
radius
,
maxLife
))
this
.
particles
.
push
(
new
CircleParticle
(
x
,
y
,
colorHex
,
vx
,
vy
,
radius
,
maxLife
))
}
}
// ── 冲击波圆环(主消除才有,只保留1圈)
// ── 白色 & 黄色小圆粒子(高光碎片)
const
glintCount
=
isFloating
?
Math
.
floor
(
cfg
.
glintCount
*
0.4
)
:
cfg
.
glintCount
for
(
let
i
=
0
;
i
<
glintCount
;
i
++
)
{
const
angle
=
Math
.
random
()
*
Math
.
PI
*
2
const
speed
=
3
+
Math
.
random
()
*
4
const
glintColor
=
Math
.
random
()
<
0.5
?
'#ffffff'
:
'#ffe566'
this
.
particles
.
push
(
new
CircleParticle
(
x
,
y
,
glintColor
,
Math
.
cos
(
angle
)
*
speed
,
Math
.
sin
(
angle
)
*
speed
,
R
*
0.12
,
18
+
Math
.
floor
(
Math
.
random
()
*
12
))
)
}
// ── 火花线条粒子
const
sparkCount
=
isFloating
?
Math
.
floor
(
cfg
.
sparkCount
*
0.4
)
:
cfg
.
sparkCount
for
(
let
i
=
0
;
i
<
sparkCount
;
i
++
)
{
const
angle
=
Math
.
random
()
*
Math
.
PI
*
2
const
speed
=
5
+
Math
.
random
()
*
7
const
sparkColor
=
i
%
2
===
0
?
'#ffffff'
:
colorHex
this
.
particles
.
push
(
new
SparkParticle
(
x
,
y
,
sparkColor
,
Math
.
cos
(
angle
)
*
speed
,
Math
.
sin
(
angle
)
*
speed
)
)
}
// ── 冲击波圆环
if
(
!
isFloating
)
{
if
(
!
isFloating
)
{
this
.
rings
.
push
(
new
ShockRing
(
x
,
y
,
colorHex
,
0
))
this
.
rings
.
push
(
new
ShockRing
(
x
,
y
,
colorHex
,
0
))
if
(
cfg
.
ringCount
>=
2
)
{
this
.
rings
.
push
(
new
ShockRing
(
x
,
y
,
'#ffffff'
,
5
))
}
}
}
}
}
...
@@ -192,30 +224,57 @@ export class Explosion {
...
@@ -192,30 +224,57 @@ export class Explosion {
}
}
render
(
ctx
)
{
render
(
ctx
)
{
//
闪光光晕(径向渐变
)
//
── 闪光光晕(简单实心圆,性能更好
)
if
(
this
.
flashLife
>
0
)
{
if
(
this
.
flashLife
>
0
)
{
const
t
=
this
.
flashLife
/
10
const
t
=
this
.
flashLife
/
10
ctx
.
save
()
ctx
.
save
()
ctx
.
globalAlpha
=
t
*
0.8
ctx
.
globalAlpha
=
t
*
0.6
const
grad
=
ctx
.
createRadialGradient
(
ctx
.
fillStyle
=
'#ffffff'
this
.
_x
,
this
.
_y
,
0
,
ctx
.
beginPath
()
this
.
_x
,
this
.
_y
,
BUBBLE_RADIUS
*
1.6
ctx
.
arc
(
this
.
_x
,
this
.
_y
,
BUBBLE_RADIUS
*
0.8
,
0
,
Math
.
PI
*
2
)
)
ctx
.
fill
()
grad
.
addColorStop
(
0
,
'#ffffff'
)
ctx
.
globalAlpha
=
t
*
0.4
grad
.
addColorStop
(
0.4
,
this
.
_colorHex
)
ctx
.
fillStyle
=
this
.
_colorHex
grad
.
addColorStop
(
1
,
'rgba(0,0,0,0)'
)
ctx
.
beginPath
()
ctx
.
beginPath
()
ctx
.
arc
(
this
.
_x
,
this
.
_y
,
BUBBLE_RADIUS
*
1.6
,
0
,
Math
.
PI
*
2
)
ctx
.
arc
(
this
.
_x
,
this
.
_y
,
BUBBLE_RADIUS
*
1.4
,
0
,
Math
.
PI
*
2
)
ctx
.
fillStyle
=
grad
ctx
.
fill
()
ctx
.
fill
()
ctx
.
restore
()
ctx
.
restore
()
}
}
// 冲击波圆环
// ── 冲击波圆环(批量渲染)
for
(
const
r
of
this
.
rings
)
r
.
render
(
ctx
)
ctx
.
save
()
ctx
.
lineWidth
=
2.5
for
(
const
r
of
this
.
rings
)
{
if
(
!
r
.
alive
||
r
.
alpha
<=
0
||
r
.
life
<=
0
)
continue
ctx
.
globalAlpha
=
r
.
alpha
ctx
.
strokeStyle
=
r
.
color
ctx
.
beginPath
()
ctx
.
arc
(
r
.
x
,
r
.
y
,
r
.
r
,
0
,
Math
.
PI
*
2
)
ctx
.
stroke
()
}
ctx
.
restore
()
// 粒子
// ── 粒子批量渲染(圆形粒子 + 火花线条)
for
(
const
p
of
this
.
particles
)
p
.
render
(
ctx
)
ctx
.
save
()
for
(
const
p
of
this
.
particles
)
{
if
(
!
p
.
alive
||
p
.
alpha
<=
0
)
continue
ctx
.
globalAlpha
=
p
.
alpha
if
(
p
.
isSpark
)
{
ctx
.
strokeStyle
=
p
.
color
ctx
.
lineWidth
=
1.8
ctx
.
lineCap
=
'round'
ctx
.
beginPath
()
ctx
.
moveTo
(
p
.
x
,
p
.
y
)
ctx
.
lineTo
(
p
.
prevX
,
p
.
prevY
)
ctx
.
stroke
()
}
else
{
ctx
.
fillStyle
=
p
.
color
ctx
.
beginPath
()
ctx
.
arc
(
p
.
x
,
p
.
y
,
p
.
radius
,
0
,
Math
.
PI
*
2
)
ctx
.
fill
()
}
}
ctx
.
restore
()
}
}
}
}
...
@@ -234,16 +293,25 @@ export function updateAndDrawExplosions(ctx, explosionList) {
...
@@ -234,16 +293,25 @@ export function updateAndDrawExplosions(ctx, explosionList) {
}
}
}
}
/** 同屏最大爆炸实例数,超出丢弃避免卡顿 */
/**
const
MAX_EXPLOSIONS
=
12
* 单玩家最大爆炸实例数,超出丢弃避免卡顿
* 按玩家独立计算,避免4人同屏时互相抢占额度
* 考虑到一次消除可能有3-10个泡泡,悬空掉落可能更多,设置足够大的值
*/
const
MAX_EXPLOSIONS_PER_PLAYER
=
50
/**
/**
* 根据状态中的新爆炸事件追加 Explosion 实例
* 根据状态中的新爆炸事件追加 Explosion 实例
* 确保每个消除的泡泡都有爆炸效果
*/
*/
export
function
appendExplosionsFromState
(
explosionList
,
newExplosions
)
{
export
function
appendExplosionsFromState
(
explosionList
,
newExplosions
)
{
if
(
!
newExplosions
||
!
newExplosions
.
length
)
return
if
(
!
newExplosions
||
!
newExplosions
.
length
)
return
for
(
const
{
x
,
y
,
colorHex
,
color
}
of
newExplosions
)
{
if
(
explosionList
.
length
>=
MAX_EXPLOSIONS
)
break
// 如果新爆炸数量超过限制,优先保留前面的(确保每个泡泡都有爆炸)
const
availableSlots
=
Math
.
max
(
0
,
MAX_EXPLOSIONS_PER_PLAYER
-
explosionList
.
length
)
const
explosionsToAdd
=
newExplosions
.
slice
(
0
,
availableSlots
)
for
(
const
{
x
,
y
,
colorHex
,
color
}
of
explosionsToAdd
)
{
// 优先用 colorHex(小游戏序列化传来的十六进制),回退到颜色索引
// 优先用 colorHex(小游戏序列化传来的十六进制),回退到颜色索引
const
c
=
colorHex
||
color
||
1
const
c
=
colorHex
||
color
||
1
explosionList
.
push
(
new
Explosion
(
x
,
y
,
c
,
false
))
explosionList
.
push
(
new
Explosion
(
x
,
y
,
c
,
false
))
...
...
minigame-1
@
8ab3c7c4
Subproject commit
d88c2a7b167e135b0e3a33f7f72a42d5d7eb9b5b
Subproject commit
8ab3c7c4b7f885eea0ef9c4d9f51f3fa08188665
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论