Skip to content

Commit 97cbbed

Browse files
committed
完成雷电效果
1 parent 269ced2 commit 97cbbed

File tree

9 files changed

+235
-47
lines changed

9 files changed

+235
-47
lines changed

src/config/AssetKeys.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ export const VFXTextureKeys = {
8282
VfxNoiseBar: 'vfx_noise_bar',
8383
// 对应截图上方的圆环图 (用来做冲击波)
8484
VfxRing: 'vfx_ring',
85+
VfxLightningLine: 'vfx_lightning_line',
86+
VfxAlertIcon: 'vfx_alert_icon',
87+
VfxAlertBg: 'vfx_alert_bg',
8588
} as const;
8689
export type VFXTextureKey = typeof VFXTextureKeys[keyof typeof VFXTextureKeys];
8790

src/config/AssetManifest.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,26 @@ export interface IAssetDefinition {
99
frameConfig?: Phaser.Types.Loader.FileTypes.ImageFrameConfig; // 用于精灵表
1010
}
1111

12+
const createSpriteSheet = (
13+
key: string,
14+
path: string,
15+
frameWidth: number,
16+
frameHeight: number,
17+
_frameMax?: number,
18+
margin: number = 0,
19+
spacing: number = 0
20+
): IAssetDefinition => ({
21+
key,
22+
path,
23+
type: 'spritesheet',
24+
frameConfig: {
25+
frameWidth,
26+
frameHeight,
27+
// frameMax,
28+
margin,
29+
spacing
30+
}
31+
});
1232
const SkinConfigs = [
1333
{ baseKey: TextureKeys.DefaultYellowKite, prefix: 'kite_default_yellow',
1434
parts: ['body_main', 'string', 'knot'] },
@@ -95,6 +115,10 @@ export const AssetManifest: IAssetDefinition[] = [
95115
{ key: VFXTextureKeys.VfxNoiseBar, path: 'assets/vfx/noise_bar.png', type: 'image' },
96116
// 请将截图里的 "FX_TEX_Circle_Ring_Wave_01.png" 改名为 ring.png
97117
{ key: VFXTextureKeys.VfxRing, path: 'assets/vfx/ring.png', type: 'image' },
118+
createSpriteSheet(VFXTextureKeys.VfxLightningLine, 'assets/vfx/FX_TEX_Lightning_Line_03a.png', 256, 64, 4),
119+
{ key: VFXTextureKeys.VfxAlertIcon, path: 'assets/vfx/FX_TEX_Alert_01.png', type: 'image' },
120+
// 3x3 网格,256 / 3 = 85.33,向下取整为 85
121+
createSpriteSheet(VFXTextureKeys.VfxAlertBg, 'assets/vfx/FX_TEX_Lightning_04.png', 85, 85, 9),
98122

99123

100124

@@ -124,3 +148,7 @@ export const AssetManifest: IAssetDefinition[] = [
124148
{ key: AudioKeys.SfxThunderstorm, path: 'assets/audio/sfx_thunderstorm.wav', type: 'audio' },
125149
{ key: AudioKeys.SfxAurora, path: 'assets/audio/sfx_aurora.mp3', type: 'audio' },
126150
]
151+
152+
153+
154+

src/entities/summons/LightningColumn.ts

Lines changed: 107 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
import { BaseSummon, type ISummonInitData } from './BaseSummon';
22
import { GameConfig } from '../../config/GameConfig';
33
import AudioManager from '../../managers/AudioManager';
4-
import { AudioKeys } from '../../config/AssetKeys';
4+
import { AudioKeys, VFXTextureKeys } from '../../config/AssetKeys';
5+
import { PipelineID } from '../../managers/RenderManager';
6+
import type LightningFadePipeline from '../../pipelines/LightningFadePipeline';
57

68
export class LightningColumn extends BaseSummon {
79
private isStriking: boolean = false; // 是否处于伤害阶段
810
private warningRect?: Phaser.GameObjects.Rectangle; // 预警线
9-
private strikeRect?: Phaser.GameObjects.Rectangle; // 正式闪电
11+
private alertIcon?: Phaser.GameObjects.Image; // 预警图标
12+
private alertBg?: Phaser.GameObjects.Sprite; // 预警背景动效
13+
private strikeSprite?: Phaser.GameObjects.Sprite; // 正式闪电
14+
private strikeProgress: number = 0;
1015

1116
// 伤害配置
1217
private readonly WARNING_WIDTH = 200; // 预警线宽度
@@ -24,31 +29,61 @@ export class LightningColumn extends BaseSummon {
2429
const screenHeight = this.scene.scale.height;
2530
// const screenWidth = this.scene.scale.width;
2631
this.isStriking = false;
32+
this.strikeProgress = 0;
2733

28-
// 1. 创建预警线 (淡淡的细线)
34+
// 1. 创建预警线 (红色闪烁)
2935
// 随机 X 位置已经在 Spawn 时决定了 (this.x)
3036
this.warningRect = this.scene.add.rectangle(
31-
this.x,
32-
screenHeight / 2,
33-
this.WARNING_WIDTH,
34-
screenHeight / minZoom,
35-
0xFFFFFF, 0.3
37+
this.x,
38+
screenHeight / 2,
39+
this.WARNING_WIDTH,
40+
screenHeight / minZoom,
41+
0xFF3333, 0.3
3642
)
3743
.setScrollFactor(0)
3844
.setDepth(40); // 在玩家下面一点
3945

46+
const alertAnimKey = 'alert_lightning_bg';
47+
if (!this.scene.anims.exists(alertAnimKey)) {
48+
this.scene.anims.create({
49+
key: alertAnimKey,
50+
frames: this.scene.anims.generateFrameNumbers(VFXTextureKeys.VfxAlertBg, { start: 0, end: 8 }),
51+
frameRate: 12,
52+
repeat: -1
53+
});
54+
}
55+
56+
this.alertBg = this.scene.add.sprite(this.x, screenHeight / 2, VFXTextureKeys.VfxAlertBg)
57+
.setScrollFactor(0)
58+
.setDepth(41)
59+
.setScale(2.5);
60+
this.alertBg.play(alertAnimKey);
61+
62+
this.alertIcon = this.scene.add.image(this.x, screenHeight / 2, VFXTextureKeys.VfxAlertIcon)
63+
.setScrollFactor(0)
64+
.setDepth(42)
65+
.setScale(0.5);
66+
4067
// 2. 预警动画 (1.5秒后劈下)
4168
this.scene.tweens.add({
4269
targets: this.warningRect,
43-
alpha: { from: 0.1, to: 0.5 },
70+
alpha: { from: 0.1, to: 0.7 },
4471
yoyo: true,
45-
repeat: 3,
72+
repeat: 4,
4673
duration: 200,
4774
onComplete: () => {
4875
this.strike();
4976
}
5077
});
51-
78+
79+
this.scene.tweens.add({
80+
targets: this.alertIcon,
81+
alpha: { from: 0.2, to: 1 },
82+
yoyo: true,
83+
repeat: 4,
84+
duration: 200
85+
});
86+
5287
// 3. 禁用物理 (手动判定)
5388
if (this.body) this.body.enable = false;
5489
}
@@ -62,45 +97,71 @@ export class LightningColumn extends BaseSummon {
6297

6398
// 移除预警
6499
this.warningRect?.destroy();
100+
this.alertIcon?.destroy();
101+
this.alertBg?.destroy();
65102

66-
// 创建闪电 (高亮粗柱子)
67-
// 颜色:雷电紫/白
68-
this.strikeRect = this.scene.add.rectangle(
69-
this.x,
70-
screenHeight / 2,
71-
this.DAMAGE_WIDTH,
72-
screenHeight / minZoom,
73-
0x8844FF
103+
const frameIndex = Phaser.Math.Between(0, 5);
104+
this.strikeSprite = this.scene.add.sprite(
105+
this.x,
106+
screenHeight / 2,
107+
VFXTextureKeys.VfxLightningLine,
108+
frameIndex
74109
)
75110
.setScrollFactor(0)
76111
.setDepth(100); // 最上层
112+
this.strikeSprite.setRotation(Math.PI / 2);
113+
this.strikeSprite.setDisplaySize(screenHeight / minZoom, this.DAMAGE_WIDTH);
77114

78115
AudioManager.playSfx(AudioKeys.SfxThunderbolt);
116+
117+
let pipeline: LightningFadePipeline | undefined;
118+
if (this.scene.game.renderer instanceof Phaser.Renderer.WebGL.WebGLRenderer) {
119+
this.strikeSprite.setPipeline(PipelineID.LightningFade);
120+
pipeline = this.strikeSprite.pipeline as LightningFadePipeline;
121+
pipeline?.setProgress(0);
122+
// pipeline?.setColor(0x3aa0ff);
123+
}
79124

80-
// 闪电冲击动画 (0.2秒瞬间)
125+
// 闪电冲击动画 (0.5秒进度 + 0.5秒淡出)
81126
this.scene.tweens.add({
82-
targets: this.strikeRect,
83-
alpha: { from: 1, to: 0 },
84-
width: { from: this.DAMAGE_WIDTH, to: 0 },
85-
duration: 300,
127+
targets: this,
128+
strikeProgress: 1,
129+
duration: 500,
86130
ease: 'Sine.easeOut',
131+
onUpdate: () => {
132+
if (pipeline) {
133+
pipeline.setProgress(this.strikeProgress);
134+
}
135+
},
87136
onComplete: () => {
88-
// ✅ 1. 视觉上:彻底移除闪电,让玩家以为结束了
89-
if (this.strikeRect) {
90-
this.strikeRect.destroy();
91-
this.strikeRect = undefined;
137+
if (!this.strikeSprite) {
138+
this.isStriking = false;
139+
return;
92140
}
93-
94-
// ✅ 2. 逻辑上:强制关闭伤害判定 (防止隐形电人)
95-
this.isStriking = false;
96-
97-
// ✅ 3. 核心修改:延迟 2秒 再真正回收
98-
// 这 2秒 期间,getActiveCount() 依然是 1,
99-
// 所以 LightningStrikeAction 会一直返回,不会生成新雷
100-
const COOLDOWN_TIME = 2000;
101-
102-
this.scene.time.delayedCall(COOLDOWN_TIME, () => {
103-
this.despawn();
141+
this.scene.tweens.add({
142+
targets: this.strikeSprite,
143+
alpha: 0,
144+
duration: 500,
145+
ease: 'Sine.easeOut',
146+
onComplete: () => {
147+
// ? 1. 视觉上:彻底移除闪电,让玩家以为结束了
148+
if (this.strikeSprite) {
149+
this.strikeSprite.destroy();
150+
this.strikeSprite = undefined;
151+
}
152+
153+
// ? 2. 逻辑上:强制关闭伤害判定 (防止隐形电人)
154+
this.isStriking = false;
155+
156+
// ? 3. 核心修改:延迟 2秒 再真正回收
157+
// 这 2秒 期间,getActiveCount() 依然是 1,
158+
// 所以 LightningStrikeAction 会一直返回,不会生成新雷
159+
const COOLDOWN_TIME = 2000;
160+
161+
this.scene.time.delayedCall(COOLDOWN_TIME, () => {
162+
this.despawn();
163+
});
164+
}
104165
});
105166
}
106167
});
@@ -111,8 +172,8 @@ export class LightningColumn extends BaseSummon {
111172

112173
protected onUpdate(_dt: number): void {
113174
// 只有在劈下的一瞬间 (isStriking) 且 闪电还没完全消失时判定
114-
// ✅ 增加 check:如果 strikeRect 已经被销毁了 (处于幽灵冷却期),直接返回
115-
if (!this.isStriking || !this.strikeRect || this.strikeRect.alpha < 0.5) return;
175+
// ✅ 增加 check:如果 strikeSprite 已经被销毁了 (处于幽灵冷却期),直接返回
176+
if (!this.isStriking || !this.strikeSprite || this.strikeProgress >= 1) return;
116177
if (!this.target || !this.target.active) return;
117178

118179
// --- 碰撞判定 (屏幕空间) ---
@@ -154,7 +215,10 @@ export class LightningColumn extends BaseSummon {
154215

155216
protected override onDespawn(): void {
156217
this.warningRect?.destroy();
157-
this.strikeRect?.destroy();
218+
this.strikeSprite?.destroy();
158219
this.kill();
159220
}
160-
}
221+
}
222+
223+
224+

src/managers/BackgroundManager.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export default class BackgroundManager {
2121
private readonly PARALLAX_X = 0.5;
2222
private readonly CLOUD_SPEED_MULT = 2.0;
2323
// ✅ 新增:缝隙修复像素 (让图片产生微小重叠,消除接缝闪烁)
24-
private readonly OVERLAP_FIX = 2;
24+
private readonly OVERLAP_FIX = 4;
2525

2626
// --- 尺寸与定位 ---
2727
private scaleRatio: number = 1;

src/managers/RenderManager.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ import FogPipeline from '../pipelines/FogPipeline';
33
import MagicFieldPipeline from '../pipelines/MagicFieldPipeline';
44
import DissolvePipeline from '../pipelines/DissolvePipeline';
55
import ColdnessPipeline from '../pipelines/ColdnessPipeline';
6+
import LightningFadePipeline from '../pipelines/LightningFadePipeline';
67

78
export const PipelineID = {
89
Fog: 'FogPipeline',
910
MagicField: 'MagicField',
1011
Dissolve: 'Dissolve',
12+
LightningFade: 'LightningFade',
1113
Coldness: 'Coldness', // ✅ Add ID
1214
} as const;
1315
export type PipelineKey = typeof PipelineID[keyof typeof PipelineID];
@@ -50,6 +52,12 @@ export default class RenderManager {
5052
this.renderer.pipelines.add(PipelineID.Coldness, new ColdnessPipeline(this.scene.game));
5153
console.log(`[RenderManager] Pipeline Registered: ${PipelineID.Coldness}`);
5254
}
55+
56+
// ✅ 注册 LightningFade Pipeline
57+
if (!this.renderer.pipelines.has(PipelineID.LightningFade)) {
58+
this.renderer.pipelines.add(PipelineID.LightningFade, new LightningFadePipeline(this.scene.game));
59+
console.log(`[RenderManager] Pipeline Registered: ${PipelineID.LightningFade}`);
60+
}
5361
}
5462

5563
// ✅ 3. 公开获取 Pipeline 的方法
@@ -70,4 +78,5 @@ export default class RenderManager {
7078
public update(_time: number, _delta: number) {
7179
// 预留 update 接口
7280
}
73-
}
81+
}
82+
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import Phaser from 'phaser';
2+
import LightningFadeVert from '../shaders/LightningFade.vert?raw';
3+
import LightningFadeFrag from '../shaders/LightningFade.frag?raw';
4+
5+
export default class LightningFadePipeline extends Phaser.Renderer.WebGL.Pipelines.MultiPipeline {
6+
private _progress: number = 0;
7+
private _color = new Phaser.Display.Color(0, 0, 255);
8+
9+
constructor(game: Phaser.Game) {
10+
const safeFragShader = LightningFadeFrag.replace(/%count%/gi, '1');
11+
super({
12+
game,
13+
vertShader: LightningFadeVert,
14+
fragShader: safeFragShader,
15+
});
16+
}
17+
18+
onPreRender() {
19+
this.set1f('uProgress', this._progress);
20+
this.set3f('uColor', this._color.redGL, this._color.greenGL, this._color.blueGL);
21+
}
22+
23+
public setProgress(value: number) {
24+
this._progress = Phaser.Math.Clamp(value, 0, 1);
25+
}
26+
27+
public setColor(color: number) {
28+
this._color = Phaser.Display.Color.IntegerToColor(color);
29+
}
30+
}

src/scenes/PreloadScene.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@ export default class PreloadScene extends Phaser.Scene {
6767
// 资源加载完毕,准备进入游戏
6868
AudioManager.init(this.game);
6969

70-
// this.scene.start(SceneKeys.Lab);
71-
this.scene.start(SceneKeys.MainMenu);
70+
this.scene.start(SceneKeys.Lab);
71+
// this.scene.start(SceneKeys.MainMenu);
7272
// // 🧪 检查 URL 参数,决定是进实验室还是进游戏
7373
// const urlParams = new URLSearchParams(window.location.search);
7474
// if (urlParams.has('lab')) {

src/shaders/LightningFade.frag

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#define SHADER_NAME LIGHTNING_FADE_FS
2+
precision mediump float;
3+
4+
uniform sampler2D uMainSampler[%count%];
5+
uniform float uProgress; // 0.0 - 1.0, reveal top to bottom
6+
uniform vec3 uColor;
7+
8+
varying vec2 outTexCoord;
9+
varying float outTexId;
10+
varying float outTintEffect;
11+
varying vec4 outTint;
12+
13+
void main()
14+
{
15+
vec4 texColor = texture2D(uMainSampler[0], outTexCoord);
16+
17+
float softness = 0.05;
18+
float mask = 1.0 - smoothstep(uProgress, uProgress + softness, outTexCoord.y);
19+
// float luminance = dot(texColor.rgb, vec3(0.299, 0.587, 0.114));
20+
21+
vec4 color;
22+
// color.rgb = uColor * luminance;
23+
color.rgb = texColor.rgb;
24+
// color.a = mask * luminance * outTint.a;
25+
color.a = mask * texColor.a;
26+
color.rgb *= color.a;
27+
28+
// gl_FragColor = color;
29+
gl_FragColor = color;
30+
}

0 commit comments

Comments
 (0)