refactor(skill): 重构技能配置加载逻辑,将参数下沉至prefab配置

1. 新增SkillView组件属性runType/endType/speed/time,替换原配置表字段
2. 批量同步所有技能prefab的配置参数
3. 优化技能运行逻辑,优先使用prefab配置,配置表仅作兜底
4. 新增同步脚本用于批量迁移技能配置
This commit is contained in:
pan
2026-08-13 16:26:19 +08:00
parent c6b5e1431f
commit 695a9e2b66
22 changed files with 268 additions and 95 deletions

View File

@@ -250,7 +250,11 @@
}, },
"atk_x": 10, "atk_x": 10,
"atk_y": 15, "atk_y": 15,
"_id": "" "_id": "",
"runType": 1,
"endType": 2,
"speed": 720,
"time": 0
}, },
{ {
"__type__": "cc.CompPrefabInfo", "__type__": "cc.CompPrefabInfo",

View File

@@ -250,7 +250,11 @@
}, },
"atk_x": 10, "atk_x": 10,
"atk_y": 15, "atk_y": 15,
"_id": "" "_id": "",
"runType": 1,
"endType": 2,
"speed": 720,
"time": 0
}, },
{ {
"__type__": "cc.CompPrefabInfo", "__type__": "cc.CompPrefabInfo",

View File

@@ -250,7 +250,11 @@
}, },
"atk_x": 10, "atk_x": 10,
"atk_y": 15, "atk_y": 15,
"_id": "" "_id": "",
"runType": 1,
"endType": 2,
"speed": 720,
"time": 0
}, },
{ {
"__type__": "cc.CompPrefabInfo", "__type__": "cc.CompPrefabInfo",

View File

@@ -250,7 +250,11 @@
}, },
"atk_x": 10, "atk_x": 10,
"atk_y": 15, "atk_y": 15,
"_id": "" "_id": "",
"runType": 1,
"endType": 2,
"speed": 720,
"time": 0
}, },
{ {
"__type__": "cc.CompPrefabInfo", "__type__": "cc.CompPrefabInfo",

View File

@@ -250,7 +250,11 @@
}, },
"atk_x": 10, "atk_x": 10,
"atk_y": 15, "atk_y": 15,
"_id": "" "_id": "",
"runType": 1,
"endType": 2,
"speed": 720,
"time": 0
}, },
{ {
"__type__": "cc.CompPrefabInfo", "__type__": "cc.CompPrefabInfo",

View File

@@ -108,8 +108,8 @@
}, },
"_lscale": { "_lscale": {
"__type__": "cc.Vec3", "__type__": "cc.Vec3",
"x": 1.6, "x": 1.3,
"y": 1.4, "y": 1.1,
"z": 1 "z": 1
}, },
"_mobility": 0, "_mobility": 0,
@@ -252,7 +252,11 @@
"__id__": 11 "__id__": 11
}, },
"atk_x": 20, "atk_x": 20,
"atk_y": 30, "atk_y": 40,
"runType": 2,
"endType": 2,
"speed": 720,
"time": 0,
"_id": "" "_id": ""
}, },
{ {

View File

@@ -282,7 +282,11 @@
}, },
"atk_x": 10, "atk_x": 10,
"atk_y": 25, "atk_y": 25,
"_id": "" "_id": "",
"runType": 1,
"endType": 2,
"speed": 720,
"time": 0
}, },
{ {
"__type__": "cc.CompPrefabInfo", "__type__": "cc.CompPrefabInfo",

View File

@@ -282,7 +282,11 @@
}, },
"atk_x": 10, "atk_x": 10,
"atk_y": 25, "atk_y": 25,
"_id": "" "_id": "",
"runType": 1,
"endType": 2,
"speed": 720,
"time": 0
}, },
{ {
"__type__": "cc.CompPrefabInfo", "__type__": "cc.CompPrefabInfo",

View File

@@ -282,7 +282,11 @@
}, },
"atk_x": 10, "atk_x": 10,
"atk_y": 25, "atk_y": 25,
"_id": "" "_id": "",
"runType": 1,
"endType": 2,
"speed": 720,
"time": 0
}, },
{ {
"__type__": "cc.CompPrefabInfo", "__type__": "cc.CompPrefabInfo",

View File

@@ -250,7 +250,11 @@
}, },
"atk_x": 10, "atk_x": 10,
"atk_y": 15, "atk_y": 15,
"_id": "" "_id": "",
"runType": 1,
"endType": 2,
"speed": 720,
"time": 0
}, },
{ {
"__type__": "cc.CompPrefabInfo", "__type__": "cc.CompPrefabInfo",

View File

@@ -250,7 +250,11 @@
}, },
"atk_x": 10, "atk_x": 10,
"atk_y": 15, "atk_y": 15,
"_id": "" "_id": "",
"runType": 1,
"endType": 2,
"speed": 720,
"time": 0
}, },
{ {
"__type__": "cc.CompPrefabInfo", "__type__": "cc.CompPrefabInfo",

View File

@@ -253,7 +253,11 @@
}, },
"atk_x": 0, "atk_x": 0,
"atk_y": 0, "atk_y": 0,
"_id": "" "_id": "",
"runType": 1,
"endType": 2,
"speed": 720,
"time": 0
}, },
{ {
"__type__": "cc.CompPrefabInfo", "__type__": "cc.CompPrefabInfo",

View File

@@ -389,7 +389,11 @@
}, },
"atk_x": 0, "atk_x": 0,
"atk_y": 0, "atk_y": 0,
"_id": "" "_id": "",
"runType": 0,
"endType": 2,
"speed": 720,
"time": 0
}, },
{ {
"__type__": "cc.CompPrefabInfo", "__type__": "cc.CompPrefabInfo",

View File

@@ -250,7 +250,11 @@
}, },
"atk_x": 10, "atk_x": 10,
"atk_y": 15, "atk_y": 15,
"_id": "" "_id": "",
"runType": 1,
"endType": 2,
"speed": 720,
"time": 0
}, },
{ {
"__type__": "cc.CompPrefabInfo", "__type__": "cc.CompPrefabInfo",

View File

@@ -250,7 +250,11 @@
}, },
"atk_x": 10, "atk_x": 10,
"atk_y": 15, "atk_y": 15,
"_id": "" "_id": "",
"runType": 1,
"endType": 2,
"speed": 720,
"time": 0
}, },
{ {
"__type__": "cc.CompPrefabInfo", "__type__": "cc.CompPrefabInfo",

View File

@@ -250,7 +250,11 @@
}, },
"atk_x": 10, "atk_x": 10,
"atk_y": 15, "atk_y": 15,
"_id": "" "_id": "",
"runType": 1,
"endType": 2,
"speed": 720,
"time": 0
}, },
{ {
"__type__": "cc.CompPrefabInfo", "__type__": "cc.CompPrefabInfo",

View File

@@ -250,7 +250,11 @@
}, },
"atk_x": 10, "atk_x": 10,
"atk_y": 15, "atk_y": 15,
"_id": "" "_id": "",
"runType": 1,
"endType": 2,
"speed": 720,
"time": 0
}, },
{ {
"__type__": "cc.CompPrefabInfo", "__type__": "cc.CompPrefabInfo",

View File

@@ -1,4 +1,4 @@
// ========== 从 HeroAttrs.ts 导入属性相关定义 ========== // ========== 从 HeroAttrs.ts 导入属性相关定义 ==========
import { Attrs } from "./HeroAttrs"; import { Attrs } from "./HeroAttrs";
export enum HSSet { export enum HSSet {
@@ -134,6 +134,7 @@ export interface SkillConfig {
gold?: number, // 获取金币技能的金币值 gold?: number, // 获取金币技能的金币值
hit_count: number, // 可命中次数 hit_count: number, // 可命中次数
hitcd: number, // 持续伤害的伤害间隔(秒) hitcd: number, // 持续伤害的伤害间隔(秒)
/** @deprecated 已下沉到 SkillView @propertyprefab 配置),此处仅作兜底 */
speed: number, // 移动速度 speed: number, // 移动速度
with: number, // 宽度(暂时无用) with: number, // 宽度(暂时无用)
ready: number, // 前摇时间 ready: number, // 前摇时间
@@ -142,11 +143,14 @@ export interface SkillConfig {
EAnm: number, // 结束动画ID EAnm: number, // 结束动画ID
DAnm: string, // 命中后动画ID DAnm: string, // 命中后动画ID
IType: IType, // 技能类型(近战/远程/辅助) IType: IType, // 技能类型(近战/远程/辅助)
/** @deprecated 已下沉到 SkillView @propertyprefab 配置),此处仅作兜底 */
RType: RType, // 技能运行类型(直线/贝塞尔/固定起点/固定终点) RType: RType, // 技能运行类型(直线/贝塞尔/固定起点/固定终点)
/** @deprecated 已下沉到 SkillView @propertyprefab 配置),此处仅作兜底 */
EType: EType, // 结束条件(动画结束/时间结束/距离结束/碰撞/次数结束) EType: EType, // 结束条件(动画结束/时间结束/距离结束/碰撞/次数结束)
bezier_start_y?: number, // 贝塞尔起始抬升高度 bezier_start_y?: number, // 贝塞尔起始抬升高度
bezier_mid_y?: number, // 贝塞尔中间高度增量 bezier_mid_y?: number, // 贝塞尔中间高度增量
bezier_arc?: number, // 贝塞尔弧度系数 bezier_arc?: number, // 贝塞尔弧度系数
/** @deprecated 已下沉到 SkillView @propertyprefab 配置),此处仅作兜底 */
time?: number, // timeEnd 持续时间(秒) time?: number, // timeEnd 持续时间(秒)
kind?: SkillKind, // 主效果类型 kind?: SkillKind, // 主效果类型
crt?: number, // 额外暴击率 crt?: number, // 额外暴击率

View File

@@ -1,4 +1,4 @@
import { v3, Vec3 ,Node} from "cc"; import { v3, Vec3, Node } from "cc";
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS"; import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { EType, RType, SkillSet } from "../common/config/SkillSet"; import { EType, RType, SkillSet } from "../common/config/SkillSet";
import { SMoveDataComp } from "./SMoveComp"; import { SMoveDataComp } from "./SMoveComp";
@@ -40,8 +40,8 @@ export class SMoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
return; return;
} }
// 根据配置设置移动速度与加速度 // 移动参数由 prefab 的 SkillView @property 决定speed<=0 时 Skill.load 已回退配置表值)
if (skillConfig.speed > 0) { if (moveComp.speed <= 0) {
moveComp.speed = skillConfig.speed; moveComp.speed = skillConfig.speed;
} }
if (skillConfig.is_accel) { if (skillConfig.is_accel) {
@@ -65,7 +65,7 @@ export class SMoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
private initializePosition(moveComp: SMoveDataComp, skillView: SkillView): void { private initializePosition(moveComp: SMoveDataComp, skillView: SkillView): void {
const node = skillView.node; const node = skillView.node;
switch(moveComp.runType) { switch (moveComp.runType) {
case RType.linear: case RType.linear:
// linear类型根据atk_x和atk_y调整起始位置 // linear类型根据atk_x和atk_y调整起始位置
const adjustedStartPos = v3( const adjustedStartPos = v3(
@@ -154,8 +154,8 @@ export class SMoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
} }
update(entity: ecs.Entity): void { update(entity: ecs.Entity): void {
if(!smc.mission.play ) return; if (!smc.mission.play) return;
if(smc.mission.pause) return if (smc.mission.pause) return
const moveComp = entity.get(SMoveDataComp); const moveComp = entity.get(SMoveDataComp);
const skillView = entity.get(SkillView); const skillView = entity.get(SkillView);
@@ -205,12 +205,12 @@ export class SMoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
const twoOneMinusTt = 2 * oneMinusT * t; const twoOneMinusTt = 2 * oneMinusT * t;
nextPos.x = oneMinusTSquared * moveComp.startPos.x + nextPos.x = oneMinusTSquared * moveComp.startPos.x +
twoOneMinusTt * moveComp.controlPoint.x + twoOneMinusTt * moveComp.controlPoint.x +
tSquared * moveComp.targetPos.x; tSquared * moveComp.targetPos.x;
nextPos.y = oneMinusTSquared * moveComp.startPos.y + nextPos.y = oneMinusTSquared * moveComp.startPos.y +
twoOneMinusTt * moveComp.controlPoint.y + twoOneMinusTt * moveComp.controlPoint.y +
tSquared * moveComp.targetPos.y; tSquared * moveComp.targetPos.y;
// 计算方向角度 // 计算方向角度
const direction = new Vec3(); const direction = new Vec3();

View File

@@ -146,7 +146,7 @@ export class Skill extends ecs.Entity {
if (node.isValid) node.destroy(); if (node.isValid) node.destroy();
return; return;
} }
if (config.EType != EType.collision) { if (SView.endType != EType.collision) {
const collider = node.getComponent(BoxCollider2D); const collider = node.getComponent(BoxCollider2D);
if (collider) { if (collider) {
collider.enabled = false collider.enabled = false
@@ -174,8 +174,10 @@ export class Skill extends ecs.Entity {
sMoveCom.targetPos.set(targetPos); sMoveCom.targetPos.set(targetPos);
sMoveCom.s_uuid = s_uuid; sMoveCom.s_uuid = s_uuid;
sMoveCom.scale = caster.node.scale.x < 0 ? -1 : 1; sMoveCom.scale = caster.node.scale.x < 0 ? -1 : 1;
sMoveCom.runType = config.RType; // runType/endType/speed/time 由 prefab 的 SkillView @property 决定,配置表同名字段仅作兜底
sMoveCom.endType = config.EType; sMoveCom.runType = SView.runType;
sMoveCom.endType = SView.endType;
sMoveCom.speed = SView.speed > 0 ? SView.speed : config.speed;
sMoveCom.bezierStartHeight = config.bezier_start_y ?? sMoveCom.bezierStartHeight; sMoveCom.bezierStartHeight = config.bezier_start_y ?? sMoveCom.bezierStartHeight;
sMoveCom.bezierMidHeight = config.bezier_mid_y ?? sMoveCom.bezierMidHeight; sMoveCom.bezierMidHeight = config.bezier_mid_y ?? sMoveCom.bezierMidHeight;
sMoveCom.bezierArc = config.bezier_arc ?? sMoveCom.bezierArc; sMoveCom.bezierArc = config.bezier_arc ?? sMoveCom.bezierArc;
@@ -183,12 +185,12 @@ export class Skill extends ecs.Entity {
sMoveCom.atk_x = SView.atk_x; sMoveCom.atk_x = SView.atk_x;
sMoveCom.atk_y = SView.atk_y; sMoveCom.atk_y = SView.atk_y;
if (config.EType === EType.timeEnd) { if (SView.endType === EType.timeEnd) {
let sTimeCom = this.get(StimeDataComp); let sTimeCom = this.get(StimeDataComp);
if (!sTimeCom) sTimeCom = this.add(StimeDataComp); if (!sTimeCom) sTimeCom = this.add(StimeDataComp);
sTimeCom.reset(); sTimeCom.reset();
sTimeCom.s_uuid = s_uuid; sTimeCom.s_uuid = s_uuid;
sTimeCom.totalTime = Math.max(1, config.time ?? 0); sTimeCom.totalTime = Math.max(1, SView.time > 0 ? SView.time : (config.time ?? 0));
sTimeCom.hitInterval = Math.max(0.5, config.hitcd || 0); sTimeCom.hitInterval = Math.max(0.5, config.hitcd || 0);
} else { } else {
const sTimeCom = this.get(StimeDataComp); const sTimeCom = this.get(StimeDataComp);

View File

@@ -1,4 +1,4 @@
import { _decorator, Animation, CCInteger, Collider2D, Contact2DType, UITransform, v3, Vec3 } from "cc"; import { _decorator, Animation, CCFloat, CCInteger, Collider2D, Contact2DType, Enum, UITransform, v3, Vec3 } from "cc";
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS"; import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp"; import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
import { HeroViewComp } from "../hero/HeroViewComp"; import { HeroViewComp } from "../hero/HeroViewComp";
@@ -22,6 +22,22 @@ export class SkillView extends CCComp {
@property({ type: CCInteger }) @property({ type: CCInteger })
atk_y: number = 0 atk_y: number = 0
/** 运动类型:直线/贝塞尔/固定起点/固定终点(由 prefab 配置,优先级高于 SkillSet */
@property({ type: Enum(RType), tooltip: "技能特效运动类型" })
runType: RType = RType.linear;
/** 消亡条件:动画结束/计时结束/碰撞次数结束(由 prefab 配置,优先级高于 SkillSet */
@property({ type: Enum(EType), tooltip: "技能特效消亡条件" })
endType: EType = EType.collision;
/** 移动速度(由 prefab 配置,<=0 时回退 SkillSet.speed */
@property({ type: CCFloat, slide: true, range: [0, 2000, 10], tooltip: "技能特效移动速度" })
speed: number = 0;
/** timeEnd 持续时间(秒),仅 endType=timeEnd 时生效(由 prefab 配置,<=0 时回退 SkillSet.time */
@property({ type: CCFloat, slide: true, range: [0, 30, 0.1], tooltip: "timeEnd 持续时间(秒)" })
time: number = 0;
private debugMode: boolean = false; private debugMode: boolean = false;
anim: Animation = null; anim: Animation = null;
@@ -45,7 +61,7 @@ export class SkillView extends CCComp {
this.collider.group = this.group; this.collider.group = this.group;
this.collider.off(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this); this.collider.off(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this);
this.collider.on(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this); this.collider.on(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this);
this.collider.enabled = this.SConf?.EType === EType.collision; this.collider.enabled = this.endType === EType.collision;
} }
if (this.node.getComponent(Animation)) { if (this.node.getComponent(Animation)) {
let anim = this.node.getComponent(Animation); let anim = this.node.getComponent(Animation);
@@ -128,13 +144,13 @@ export class SkillView extends CCComp {
onAnimationFinished() { onAnimationFinished() {
// animationEnd 类型:动画结束后再关闭碰撞并销毁 // animationEnd 类型:动画结束后再关闭碰撞并销毁
if (this.SConf.EType == EType.animationEnd) { if (this.endType == EType.animationEnd) {
this.disable_collider_now(); this.disable_collider_now();
this.ent.destroy() this.ent.destroy()
return; return;
} }
// fixed 类型不移动,无法通过移动完成销毁;动画播完作为兜底销毁时机 // fixed 类型不移动,无法通过移动完成销毁;动画播完作为兜底销毁时机
if (this.SConf.RType == RType.fixed) { if (this.runType == RType.fixed) {
this.disable_collider_now(); this.disable_collider_now();
this.ent.destroy() this.ent.destroy()
} }
@@ -142,7 +158,7 @@ export class SkillView extends CCComp {
// 动画帧事件 atk仅负责开启一帧碰撞窗口不负责伤害与计数 // 动画帧事件 atk仅负责开启一帧碰撞窗口不负责伤害与计数
public atk(args: any) { public atk(args: any) {
if (!this.SConf) return; if (!this.SConf) return;
if (this.SConf.EType == EType.collision) return if (this.endType == EType.collision) return
if (this.enable_collider_safely()) { if (this.enable_collider_safely()) {
mLogger.log(this.debugMode, 'SkillView', `[SkillView] [${this.SConf?.name}] 开启碰撞检测`); mLogger.log(this.debugMode, 'SkillView', `[SkillView] [${this.SConf?.name}] 开启碰撞检测`);
this.scheduleOnce(() => { this.scheduleOnce(() => {

View File

@@ -0,0 +1,83 @@
/**
* 一次性迁移脚本:把 SkillSet 中的 RType/EType/speed/time 刷入各技能 prefab 的 SkillView 序列化字段。
* 用法: node tools/sync-skill-prefab.js
* 注意: 直接改写 prefab JSON运行前请保证工作区已提交。
*/
const fs = require('fs');
const path = require('path');
const ROOT = path.resolve(__dirname, '..');
const SKILL_SET = path.join(ROOT, 'assets/script/game/common/config/SkillSet.ts');
const PREFAB_DIR = path.join(ROOT, 'assets/resources/game/skill/atk');
// SkillView 组件在 prefab 中的脚本类 cid
const SKILL_VIEW_CID = '57aabs7TE1J5obTAZczc+64';
const RTYPE = { linear: 0, bezier: 1, fixed: 2, fixedEnd: 3 };
const ETYPE = { animationEnd: 0, timeEnd: 1, collision: 2 };
/** 从 SkillSet.ts 解析 sp_name -> {runType, endType, speed, time} */
function parseSkillSet(src) {
const map = new Map();
// 匹配每个技能条目块
const entryRe = /\d+:\s*\{([\s\S]*?)\n\s*\},/g;
let m;
while ((m = entryRe.exec(src)) !== null) {
const body = m[1];
const sp = body.match(/sp_name:\s*"([^"]+)"/);
if (!sp) continue;
const spName = sp[1];
const rt = body.match(/RType:\s*RType\.(\w+)/);
const et = body.match(/EType:\s*EType\.(\w+)/);
const speed = body.match(/speed:\s*([\d.]+)/);
const time = body.match(/\btime:\s*([\d.]+)/);
const entry = {
runType: rt ? RTYPE[rt[1]] : undefined,
endType: et ? ETYPE[et[1]] : undefined,
speed: speed ? Number(speed[1]) : undefined,
time: time ? Number(time[1]) : 0,
};
// 多个技能可共用同一 sp_name保留首个非 undefined 组合
if (!map.has(spName)) map.set(spName, entry);
}
return map;
}
function main() {
const src = fs.readFileSync(SKILL_SET, 'utf8');
const confMap = parseSkillSet(src);
const files = fs.readdirSync(PREFAB_DIR).filter(f => f.endsWith('.prefab'));
const report = { updated: [], skipped: [], noConf: [] };
for (const file of files) {
const spName = path.basename(file, '.prefab');
const conf = confMap.get(spName);
const fp = path.join(PREFAB_DIR, file);
const json = JSON.parse(fs.readFileSync(fp, 'utf8'));
// 定位 SkillView 组件
const comp = json.find(o => o && o.__type__ === SKILL_VIEW_CID);
if (!comp) { report.skipped.push(file + ' (no SkillView)'); continue; }
if (!conf) { report.noConf.push(file); continue; }
if (conf.runType !== undefined) comp.runType = conf.runType;
if (conf.endType !== undefined) comp.endType = conf.endType;
if (conf.speed !== undefined) comp.speed = conf.speed;
comp.time = conf.time ?? 0;
fs.writeFileSync(fp, JSON.stringify(json, null, 2) + '\n', 'utf8');
report.updated.push(`${file} -> runType=${comp.runType} endType=${comp.endType} speed=${comp.speed} time=${comp.time}`);
}
console.log('=== updated ===');
report.updated.forEach(s => console.log(' ' + s));
console.log('=== no config matched ===');
report.noConf.forEach(s => console.log(' ' + s));
console.log('=== skipped ===');
report.skipped.forEach(s => console.log(' ' + s));
}
main();