diff --git a/assets/bundles/audio/sfx/weapon/刀攻击.mp3 b/assets/bundles/audio/sfx/weapon/剑攻击.mp3 similarity index 100% rename from assets/bundles/audio/sfx/weapon/刀攻击.mp3 rename to assets/bundles/audio/sfx/weapon/剑攻击.mp3 diff --git a/assets/bundles/audio/sfx/weapon/刀攻击.mp3.meta b/assets/bundles/audio/sfx/weapon/剑攻击.mp3.meta similarity index 100% rename from assets/bundles/audio/sfx/weapon/刀攻击.mp3.meta rename to assets/bundles/audio/sfx/weapon/剑攻击.mp3.meta diff --git a/assets/bundles/audio/sfx/weapon/法杖攻击.mp3 b/assets/bundles/audio/sfx/weapon/法杖攻击.mp3 new file mode 100644 index 0000000..fc87b7a Binary files /dev/null and b/assets/bundles/audio/sfx/weapon/法杖攻击.mp3 differ diff --git a/assets/bundles/audio/sfx/weapon/法杖攻击.mp3.meta b/assets/bundles/audio/sfx/weapon/法杖攻击.mp3.meta new file mode 100644 index 0000000..7cc0b5a --- /dev/null +++ b/assets/bundles/audio/sfx/weapon/法杖攻击.mp3.meta @@ -0,0 +1 @@ +{"ver":"1.0.0","importer":"audio-clip","imported":true,"uuid":"094dbe62-cda1-4deb-97f6-22c6ec70f132","files":[".json",".mp3"],"subMetas":{},"userData":{"downloadMode":0}} diff --git a/assets/bundles/first/config/Text.json b/assets/bundles/first/config/Text.json index cfea860..345862e 100644 --- a/assets/bundles/first/config/Text.json +++ b/assets/bundles/first/config/Text.json @@ -123,12 +123,24 @@ "id": 2001, "type": 10, "level": 0, - "weight": 3 + "weight": 5 }, { "id": 2002, "type": 11, "level": 0, - "weight": 2 + "weight": 5 + }, + { + "id": 3001, + "type": 12, + "level": 0, + "weight": 15 + }, + { + "id": 3002, + "type": 13, + "level": 0, + "weight": 5 } ] \ No newline at end of file diff --git a/assets/script/battle/audio/BattleAudio.ts b/assets/script/battle/audio/BattleAudio.ts index a28dab6..e3f8df4 100644 --- a/assets/script/battle/audio/BattleAudio.ts +++ b/assets/script/battle/audio/BattleAudio.ts @@ -1,5 +1,6 @@ import { TeamComp, type EntityId } from 'db://assets/mx/module/battle'; import { GAudioPath } from '../../game/ConfigRes'; +import { TableNames } from '../../game/ConfigTableData'; import { getTowerLoadoutService } from '../combat/TowerLoadoutService'; import { resetTangSengAudioState } from './TangSengAudioState'; @@ -22,6 +23,7 @@ type ActiveSfx = { priority: SfxPri; startedAt: number }; /** * 战斗音效:按现有 audio 资源名映射,去叠音 / 并发上限 / 优先级 / BGM 压低。 + * 普攻/技能按五行 Hero.herotype、宝石 Weapon.type 解析(现表 id 101+)。 */ export class BattleAudio { private static _lastPlayAt = new Map(); @@ -60,10 +62,10 @@ export class BattleAudio { /** 进战斗预载常用音效,避免首帧 async 加载导致与动画不同步 */ private static _warmUpBattleSfx(): void { - for (const name of ['刀攻击', '枪攻击', '棍攻击', '箭攻击', '孙悟空技能', '悟净技能', '李靖技能', '阎王技能', '杨戬技能音效', '金箍棒技能', '玲珑宝塔技能', '碎片掉落']) { + for (const name of ['剑攻击', '枪攻击', '棍攻击', '箭攻击','法杖攻击', '孙悟空技能', '悟净技能', '李靖技能', '阎王技能', '杨戬技能音效', '金箍棒技能', '玲珑宝塔技能', '碎片掉落']) { void gg.audio.preloadEffect({ name, path: GAudioPath.SfxWeapon }); } - for (const name of ['怪物受击', '合成', '格子放置', '砚台出字', '开场号角', 'BOSS出场', '危险警报']) { + for (const name of ['怪物受击', '合成', '格子放置', '武器拖动', '砚台出字', '可刷新通知', 'BOSS出场', '危险警报', '结算金币效果']) { void gg.audio.preloadEffect({ name, path: GAudioPath.SfxUi }); } } @@ -92,23 +94,21 @@ export class BattleAudio { static playBajiaoFanCast(): void { } - /** 同字升级 */ + /** 同级数字合成升级 */ static playUpgrade(): void { this._play('合成', GAudioPath.SfxUi, Pri.System, 0); } - /** 神将组合 */ + /** @deprecated 多格神将组合已废弃;若残留路径触发则同「合成」 */ static playCompose(): void { this._play('合成', GAudioPath.SfxUi, Pri.System, 0); } /** - * 神将合成语音(仅有资源的英雄) - * @param weapon1Id Hero.id + * @deprecated 多格神将组合已废弃,无调用时机 + * @param _weapon1Id 旧 Hero.id */ - static playComposeVoice(weapon1Id: number): void { - const name = HERO_COMPOSE_VOICE[weapon1Id]; - if (name) this._play(name, GAudioPath.SfxVoice, Pri.Voice, 1000); + static playComposeVoice(_weapon1Id: number): void { } /** @deprecated 新产品无此音效 */ @@ -200,34 +200,45 @@ export class BattleAudio { this._play('游戏失败', GAudioPath.SfxUi, Pri.Result, 0); } - /** 击杀武器碎片飞出 */ + /** 击杀宝石掉落飞出 */ static playFragmentDrop(): void { this._play('碎片掉落', GAudioPath.SfxWeapon, Pri.UI, 80); } + /** 局内波次/击杀金币飞行 */ + static playCoinFly(): void { + this._play('结算金币效果', GAudioPath.SfxUi, Pri.UI, 80); + } + /** - * 普攻 + * 普攻(按 Hero.herotype → 现有刀/枪/棍/箭)。 + * 弹道类(水弹连发等)应在真正出手时调,勿在 PresentationCue 提前播。 * @param caster 攻击实体 + * @param debounceMs 去抖;连发出手用较短间隔 */ - static playWeaponAttack(caster: EntityId): void { + static playWeaponAttack(caster: EntityId, debounceMs = 150): void { if (!this._isLocalEntity(caster)) return; - const id = getTowerLoadoutService().getLoadout(caster)?.weapon1Id; - if (id == null) return; - const name = ATTACK_SFX[id]; - if (name) this._play(name, GAudioPath.SfxWeapon, Pri.Attack, 150); + const heroId = getTowerLoadoutService().getLoadout(caster)?.weapon1Id; + if (heroId == null) return; + const name = resolveAttackSfxByHeroId(heroId); + if (!name) return; + this._play(name, GAudioPath.SfxWeapon, Pri.Attack, debounceMs); } /** - * 英雄主动或专属武器技能 + * 英雄主动或宝石武器技能 * @param caster 施法实体 - * @param isEquipSkill true=专属武器技能(金箍棒/玲珑宝塔) + * @param isEquipSkill true=宝石/武器技能 */ static playWeaponSkill(caster: EntityId, isEquipSkill = false): void { if (!this._isLocalEntity(caster)) return; - const id = getTowerLoadoutService().getLoadout(caster)?.weapon1Id; - if (id == null) return; - const name = isEquipSkill ? EQUIP_SKILL_SFX[id] : HERO_SKILL_SFX[id]; - if (name) this._play(name, GAudioPath.SfxWeapon, Pri.Combat, 0); + const loadout = getTowerLoadoutService().getLoadout(caster); + if (!loadout) return; + const name = isEquipSkill + ? resolveGemSkillSfx(loadout.weaponId) + : resolveHeroSkillSfxByHeroId(loadout.weapon1Id); + if (!name) return; + this._play(name, GAudioPath.SfxWeapon, Pri.Combat, 0); } /** @@ -344,47 +355,100 @@ export class BattleAudio { } } -/** Hero.id → 普攻音效(按 Hero.weapon:1刀 2枪 3棍 4箭) */ -const ATTACK_SFX: Record = { - 1: '刀攻击', - 2: '枪攻击', - 3: '棍攻击', +/** + * Hero.herotype → 普攻音效。 + * 1金剑 2木棍 3水法杖 4火箭 5土枪 + */ +const ATTACK_BY_HEROTYPE: Record = { + 1: '剑攻击', + 2: '棍攻击', + 3: '法杖攻击', 4: '箭攻击', - 11: '棍攻击', - 12: '刀攻击', - 13: '棍攻击', - 14: '枪攻击', - 15: '枪攻击', - 16: '刀攻击', - 17: '枪攻击', - 18: '箭攻击', - 19: '刀攻击', - 20: '刀攻击', - 21: '箭攻击', - 22: '刀攻击', + 5: '枪攻击', }; -/** Hero.id → 英雄主动技能音效 */ -const HERO_SKILL_SFX: Record = { - 11: '孙悟空技能', - 13: '悟净技能', - 17: '杨戬技能音效', - 20: '阎王技能', - 22: '李靖技能', +/** + * Hero.herotype → 英雄主动技能音效(复用现有神将技能资源)。 + * 1金剑阵→孙悟空 2木飞棍→悟净 3水暴风雪→阎王 4火箭雨→杨戬 5土冲锋→李靖 + */ +const HERO_SKILL_BY_HEROTYPE: Record = { + 1: '孙悟空技能', + 2: '悟净技能', + 3: '阎王技能', + 4: '杨戬技能音效', + 5: '李靖技能', }; -/** Hero.id → 专属武器技能音效 */ -const EQUIP_SKILL_SFX: Record = { - 11: '金箍棒技能', - 22: '玲珑宝塔技能', +/** + * 五行元素 → 宝石武器技能音效(Weapon.type 2金…6土 → element 1…5)。 + * 金→金箍棒 木→悟净 水→阎王 火→杨戬 土→玲珑宝塔 + */ +const GEM_SKILL_BY_ELEMENT: Record = { + 1: '金箍棒技能', + 2: '悟净技能', + 3: '阎王技能', + 4: '杨戬技能音效', + 5: '玲珑宝塔技能', }; -/** Hero.id → 合成语音 */ -const HERO_COMPOSE_VOICE: Record = { - 11: '孙悟空合成', - 14: '哪吒合成', - 17: '杨戬合成', - 18: '王母合成', - 19: '玉帝合成', - 22: '李靖合成', -}; +/** + * @param heroId Hero.id(五行 101+) + * @returns 普攻资源名;无法解析时 warn 并返回空 + */ +function resolveAttackSfxByHeroId(heroId: number): string { + const hero = gg.data.table.getTableData(TableNames.Hero, heroId); + if (!hero) { + console.warn('[BattleAudio] 普攻音效缺少 Hero', heroId); + return ''; + } + const herotype = Math.floor(Number(hero.herotype)); + const name = ATTACK_BY_HEROTYPE[herotype]; + if (!name) { + console.warn('[BattleAudio] 普攻音效未映射 herotype', herotype, 'heroId', heroId); + return ''; + } + return name; +} + +/** + * @param heroId Hero.id + * @returns 英雄主动技能资源名 + */ +function resolveHeroSkillSfxByHeroId(heroId: number): string { + const hero = gg.data.table.getTableData(TableNames.Hero, heroId); + if (!hero) { + console.warn('[BattleAudio] 英雄技能音效缺少 Hero', heroId); + return ''; + } + const herotype = Math.floor(Number(hero.herotype)); + const name = HERO_SKILL_BY_HEROTYPE[herotype]; + if (!name) { + console.warn('[BattleAudio] 英雄技能音效未映射 herotype', herotype, 'heroId', heroId); + return ''; + } + return name; +} + +/** + * @param weaponId Weapon.id(宝石);0 表示未镶嵌 + * @returns 宝石技能资源名 + */ +function resolveGemSkillSfx(weaponId: number): string { + if (!(weaponId > 0)) { + console.warn('[BattleAudio] 宝石技能音效缺少 weaponId'); + return ''; + } + const weapon = gg.data.table.getTableData(TableNames.Weapon, weaponId); + if (!weapon) { + console.warn('[BattleAudio] 宝石技能音效缺少 Weapon', weaponId); + return ''; + } + // Weapon.type:1通用 2金 3木 4水 5火 6土 → element 1–5 + const element = Math.floor(Number(weapon.type)) - 1; + const name = GEM_SKILL_BY_ELEMENT[element]; + if (!name) { + console.warn('[BattleAudio] 宝石技能音效未映射 Weapon.type', weapon.type, 'weaponId', weaponId); + return ''; + } + return name; +} diff --git a/assets/script/battle/presentation/SkillVisualChannel.ts b/assets/script/battle/presentation/SkillVisualChannel.ts index 439de92..a5a8a3a 100644 --- a/assets/script/battle/presentation/SkillVisualChannel.ts +++ b/assets/script/battle/presentation/SkillVisualChannel.ts @@ -12,6 +12,7 @@ import { HeroWeaponMount, WEAPON_REST_ANGLE, BOW_ATK_REST_ANGLE, BOW_RAIN_AIM_DE import { BattleSpinePlayback } from './BattleSpinePlayback'; import { glyphSpineHasSkin } from '../view/GlyphSpineSkin'; import { ensureSpineSkeletonDataRuntime } from 'db://assets/mx/module/lang/spineRuntimePatch'; +import { BattleAudio } from '../audio/BattleAudio'; import { BOW_ATK_ARROW_SPEED, SCATTER_RANGE_MUL, getBowAtkHost } from '../combat/BowAtkRuntime'; import { GBundle, GPath } from '../../game/ConfigRes'; @@ -1226,6 +1227,7 @@ export class SkillVisualChannel { host.fillScatterDegs(aimDeg, count, stepDeg, this._scatterDegs); this._weapon.setArrowVisible(false); const duration = Math.max(0.12, travel / BOW_ATK_ARROW_SPEED); + let launched = 0; for (let i = 0; i < this._scatterDegs.length; i++) { const deg = this._scatterDegs[i]; const rad = deg * Math.PI / 180; @@ -1254,7 +1256,9 @@ export class SkillVisualChannel { pierce, onConsume: pierce ? undefined : this._recycleBowArrow.bind(this, clone), }); + launched++; } + if (launched > 0) BattleAudio.playWeaponAttack(caster, 40); } /** @@ -1467,6 +1471,7 @@ export class SkillVisualChannel { buff2, onLand: land.onLand, }); + BattleAudio.playWeaponAttack(caster, 40); return true; } @@ -1648,6 +1653,7 @@ export class SkillVisualChannel { const duration = Math.max(0.12, dist / WATER_BALL_SPEED); const node = this._spawnWaterBall(this._arrowFrom); if (!node) return false; + BattleAudio.playWeaponAttack(caster, 40); SkillTransformDriver.animateParabola({ node, duration, diff --git a/assets/script/battle/skill/SkillController.ts b/assets/script/battle/skill/SkillController.ts index 2c2daaf..3f672e1 100644 --- a/assets/script/battle/skill/SkillController.ts +++ b/assets/script/battle/skill/SkillController.ts @@ -378,7 +378,10 @@ export class SkillController extends Component { } try { const heroId = getTowerLoadoutService().getLoadout(event.caster)?.weapon1Id; - if (heroId !== GONG_WEAPON1_ID && !(skill instanceof Gong)) { + // 12/13/17:音效在弹道真正出手时播(水弹连发等),避免开动画就响 + const atkHead = Math.floor(Number(event.params?.atkTypeHead ?? event.params?.atkType) || 0); + const deferAttackSfx = atkHead === 12 || atkHead === 13 || atkHead === 17; + if (heroId !== GONG_WEAPON1_ID && !(skill instanceof Gong) && !deferAttackSfx) { BattleAudio.playWeaponAttack(event.caster); } skill.onAttack(ctx); diff --git a/assets/script/interface/AutoTable.ts b/assets/script/interface/AutoTable.ts index 8ba343e..a8f1322 100644 --- a/assets/script/interface/AutoTable.ts +++ b/assets/script/interface/AutoTable.ts @@ -277,7 +277,11 @@ declare global { 2、木 3、水 4、火 -5、土 */ +5、土 +10、双倍符(数字等级+1) +11、4倍符(数字等级+1) +12、随机牌(随机转换数字属性) +13、衰弱符(对方数字等级-1) */ readonly type: number; /** 等级 */ readonly level: number; diff --git a/assets/script/ui/BattleFail/UIBattleFail.ts b/assets/script/ui/BattleFail/UIBattleFail.ts index 4b0b3e5..a84d35f 100644 --- a/assets/script/ui/BattleFail/UIBattleFail.ts +++ b/assets/script/ui/BattleFail/UIBattleFail.ts @@ -9,7 +9,8 @@ import { Auto_BattleFail } from './Auto_BattleFail'; import { BattleSuccessCoinFlyPresenter } from '../BattleSuccess/BattleSuccessCoinFlyPresenter'; import { StatisticsType, ItemNumType } from '../../game/GameData'; import { ItemData } from '../../game/ConfigProjectData'; -import { UIID, GAudioPath } from '../../game/ConfigRes'; +import { UIID } from '../../game/ConfigRes'; +import { BattleAudio } from '../../battle/audio/BattleAudio'; import { MatchWinStats } from '../../manager/MatchWinStats'; import { RankDataManager } from '../../manager/RankDataManager'; import { VivoAd } from '../../sdk/functions/VivoAd'; @@ -73,7 +74,7 @@ export class UIBattleFail extends Auto_BattleFail { this.btnGet.active = true; this.btnADGet.active = true; - gg.audio.playEffect({ name: '游戏失败', path: GAudioPath.SfxUi }); + BattleAudio.playFail(); const starsBefore = RankDataManager.getPlayerRankStars(); MatchWinStats.record(false, gg.game.CurentBattle?.opponentRankId ?? 0); diff --git a/assets/script/ui/BattleGame/BattlePeachCoinFlyPresenter.ts b/assets/script/ui/BattleGame/BattlePeachCoinFlyPresenter.ts index d10c69c..3815867 100644 --- a/assets/script/ui/BattleGame/BattlePeachCoinFlyPresenter.ts +++ b/assets/script/ui/BattleGame/BattlePeachCoinFlyPresenter.ts @@ -1,5 +1,6 @@ import { Label, Node, Tween, tween, UIOpacity, Vec3 } from 'cc'; import { GEvent } from 'db://assets/mx/module/event/GEvent'; +import { BattleAudio } from '../../battle/audio/BattleAudio'; import { GBundle } from '../../game/ConfigRes'; import { CoinFly } from './CoinFly'; @@ -129,6 +130,7 @@ export class BattlePeachCoinFlyPresenter { this._wavePopped = 0; this._waveSpawnFinished = false; this._waveAwaitFly = true; + BattleAudio.playCoinFly(); this._spawnOneWaveCoin(); } @@ -277,6 +279,7 @@ export class BattlePeachCoinFlyPresenter { */ playKillDrop(startWorld: Readonly, peachCount: number): void { if (peachCount <= 0) return; + BattleAudio.playCoinFly(); const n = this._spawnCoinFlies(startWorld.clone(), peachCount, null, true); if (n <= 0) { console.log('[BattlePeachCoinFly] 击杀掉桃未生成'); diff --git a/assets/script/ui/BattleSuccess/UIBattleSuccess.ts b/assets/script/ui/BattleSuccess/UIBattleSuccess.ts index 524e077..12ad70a 100644 --- a/assets/script/ui/BattleSuccess/UIBattleSuccess.ts +++ b/assets/script/ui/BattleSuccess/UIBattleSuccess.ts @@ -17,7 +17,7 @@ import { BattleSuccessCoinFlyPresenter } from './BattleSuccessCoinFlyPresenter'; import { TaskType, ItemNumType } from '../../game/GameData'; import { ItemData } from '../../game/ConfigProjectData'; import MTools from 'db://assets/mx/tools/MTools'; -import { GAudioPath } from '../../game/ConfigRes'; +import { BattleAudio } from '../../battle/audio/BattleAudio'; import { VivoAd } from '../../sdk/functions/VivoAd'; import { IPopAnimConfig } from 'db://assets/mx/module/ui/popAnim'; import { recordKeepHeroClearFromBattle } from '../../battle/bag/KeepHeroClearRecord'; @@ -165,7 +165,7 @@ export class UIBattleSuccess extends Auto_BattleSuccess { action_param: levelId, }); - gg.audio.playEffect({ name: '游戏胜利', path: GAudioPath.SfxUi }); + BattleAudio.playVictory(); MatchWinStats.record(true, gg.game.CurentBattle?.opponentRankId ?? 0); gg.sdk.reportDY('inLevel', `${gg.game.getReportDYRankLabel()}-胜利`); diff --git a/assets/script/ui/PopDailyShopOpen/UIPopDailyShopOpen.ts b/assets/script/ui/PopDailyShopOpen/UIPopDailyShopOpen.ts index 10002cb..100975d 100644 --- a/assets/script/ui/PopDailyShopOpen/UIPopDailyShopOpen.ts +++ b/assets/script/ui/PopDailyShopOpen/UIPopDailyShopOpen.ts @@ -531,7 +531,7 @@ export class UIPopDailyShopOpen extends Auto_PopDailyShopOpen { this._onOneCardClosed(index); return; } - gg.audio.playEffect({ name: '点击抽卡', path: GAudioPath.SfxUi }); + gg.audio.playEffect({ name: '点击按钮', path: GAudioPath.SfxUi }); const targetX = openFace?.isValid ? openFace.position.x : card.position.x; const targetY = openFace?.isValid ? openFace.position.y : card.position.y; const skew = this._ensureSkew(card); diff --git a/assets/script/ui/PopSelect/UIPopSelect.ts b/assets/script/ui/PopSelect/UIPopSelect.ts index 270c76a..137b4a7 100644 --- a/assets/script/ui/PopSelect/UIPopSelect.ts +++ b/assets/script/ui/PopSelect/UIPopSelect.ts @@ -422,7 +422,7 @@ export class UIPopSelect extends Auto_PopSelect { this._onOpenHalfDone(gen); return; } - gg.audio.playEffect({ name: '点击抽卡', path: GAudioPath.SfxUi }); + gg.audio.playEffect({ name: '点击按钮', path: GAudioPath.SfxUi }); const hx = this._homeScale.x; const hy = this._homeScale.y; const hz = this._homeScale.z; diff --git a/docs/需求-音效.md b/docs/需求-音效.md index 34190a3..4261b75 100644 --- a/docs/需求-音效.md +++ b/docs/需求-音效.md @@ -1,71 +1,86 @@ # 需求:音效映射 -> 日期:2026-09-08 +> 日期:2026-10-16(对齐五行数字战斗) > 资源目录:`assets/bundles/audio/` > 播放入口:`BattleAudio`、`gg.audio.playEffect` / `playMusic` -老项目 `sfx/battle`、`sfx/wave` 已删除。无对应新资源的旧播放点不再播,不用其它音效顶替。 +老项目 `sfx/battle`、`sfx/wave` 已删除。无对应新资源的旧播放点不再播。 +**当前局内**见 [需求-五行合成.md](./需求-五行合成.md):同级数字合成 + 单格上阵;普攻/技能按 **Hero.herotype / Weapon.type** 复用现有 `sfx/weapon` 资源。 ## 1. BGM | 资源 | 时机 | |------|------| | `bgm/首页背景音` | 大厅 | -| `bgm/局内背景音` | 进战斗 | +| `bgm/局内背景音` | 进战斗(`BattleAudio.playBgmBattle`) | ## 2. UI(`sfx/ui/`) | 资源 | 时机 | |------|------| -| 点击按钮 | 按钮上的 `ClickButton` 组件(`gg.audio.playClick`) | +| 点击按钮 | `ClickButton`(`gg.audio.playClick`);三选一/商店翻牌 | | 体力消耗 | 首页开战扣体力飞行 | | 匹配完成 | 匹配页切到「站」 | | 可刷新通知 | 召唤按钮首次变为可刷新 | -| 合成 | 同字升级 / 神将组合 | +| 合成 | 同级数字升级(`BagMerged` / `MapUpgraded` → `playUpgrade`) | | 怪物受击 | 本机攻击命中 | | 抽奖转盘 | 每日商店转盘每转一格 | | 格子放置 | 字落入格子 | | 武器拖动 | 局内拖字开始 | -| 游戏失败 | 失败结算打开 | -| 游戏胜利 | 胜利结算打开 | +| 游戏失败 | 失败结算(`BattleAudio.playFail`) | +| 游戏胜利 | 胜利结算(`BattleAudio.playVictory`) | | 砚台出字 | 背包刷新每冒出一个字 | -| 结算金币效果 | 胜负领取金币飞行 | -| 铲子铲地 | 铲子挖地 | +| 结算金币效果 | 胜负领取金币飞行;局内波次结算金币飞行 | +| BOSS出场 | Boss 出场弹窗 | +| 危险警报 | 怪物危机提示每轮动画 | -## 3. 语音(`sfx/voice/`) +### 2.1 现有资源、当前无播放点(勿接已删玩法) -合成神将时播对应语音(仅下列有资源):孙悟空 / 哪吒 / 杨戬 / 王母 / 玉帝 / 李靖。 +| 资源 | 说明 | +|------|------| +| 开场号角 | 开场表演怪已删 | +| 铲子铲地 | 开挖/铲子玩法已删 | +| `sfx/voice/*合成` | 多格神将组合已废弃 | -## 4. 武器(`sfx/weapon/`) +## 3. 武器(`sfx/weapon/`)— 五行复用 -### 4.1 普攻(按 Hero.weapon) +播放:`BattleAudio.playWeaponAttack` / `playWeaponSkill`(仅本机 `Team.ownerId`)。 -| weapon | 资源 | 代表 | -|--------|------|------| -| 1 | 刀攻击 | 刀、悟能、木吒、玉帝、阎王、李靖 | -| 2 | 枪攻击 | 枪、哪吒、金吒、杨戬 | -| 3 | 棍攻击 | 棍、悟空、悟净 | -| 4 | 箭攻击 | 弓、王母、嫦娥 | +- 近战等:`SkillController` PresentationCue 时播一次 +- **弹道 atk_type 12/13/17**(含水弹连发):在 `SkillVisualChannel` **真正出手**时播;连发每发一次 -### 4.2 英雄主动 +### 3.1 普攻(`Hero.herotype`) -| Hero.id | 资源 | -|---------|------| -| 11 悟空 | 孙悟空技能 | -| 13 悟净 | 悟净技能 | -| 17 杨戬 | 杨戬技能音效 | -| 20 阎王 | 阎王技能 | -| 22 李靖 | 李靖技能 | +| herotype | 资源 | +|----------|------| +| 1 金 | 剑攻击 | +| 2 木 | 棍攻击 | +| 3 水 | 法杖攻击 | +| 4 火 | 箭攻击 | +| 5 土 | 枪攻击 | -### 4.3 专属武器技能 +### 3.2 英雄主动(`Hero.herotype` → 现有技能资源) -| Skill.id | 资源 | -|----------|------| -| 20501 如意金箍棒 | 金箍棒技能 | -| 20509 玲珑宝塔 | 玲珑宝塔技能 | +| herotype | 代表技能 | 资源 | +|----------|----------|------| +| 1 金 | 剑阵 | 孙悟空技能 | +| 2 木 | 飞棍 | 悟净技能 | +| 3 水 | 暴风雪 | 阎王技能 | +| 4 火 | 箭雨 | 杨戬技能音效 | +| 5 土 | 冲锋 | 李靖技能 | + +### 3.3 宝石武器技能(`Weapon.type`:2金…6土 → element 1…5) + +| element | 资源 | +|---------|------| +| 1 金 | 金箍棒技能 | +| 2 木 | 悟净技能 | +| 3 水 | 阎王技能 | +| 4 火 | 杨戬技能音效 | +| 5 土 | 玲珑宝塔技能 | -### 4.4 局内掉落 +### 3.4 局内掉落 | 资源 | 时机 | |------|------| -| 碎片掉落 | 击杀后武器碎片飞出时 | +| 碎片掉落 | 击杀后宝石飞出时 |