feat(skill): add multi-summon positioning and interval configuration

1. 新增技能召唤物XY轴步进偏移与召唤间隔可配置项
2. 重构召唤逻辑,支持按配置对称排布召唤物
3. 替换全局固定间隔为技能预制体可配置的错峰召唤间隔
4. 优化召唤参数缓存与解析逻辑,统一参数获取流程
5. 调整maxm3技能预制体的缩放与尺寸配置作为示例
This commit is contained in:
panFD
2026-09-14 21:28:50 +08:00
parent 2dc44b2393
commit 7640957325
3 changed files with 111 additions and 57 deletions

View File

@@ -108,8 +108,8 @@
}, },
"_lscale": { "_lscale": {
"__type__": "cc.Vec3", "__type__": "cc.Vec3",
"x": 0.9, "x": 1,
"y": 1, "y": 2,
"z": 1 "z": 1
}, },
"_mobility": 0, "_mobility": 0,
@@ -136,8 +136,8 @@
}, },
"_contentSize": { "_contentSize": {
"__type__": "cc.Size", "__type__": "cc.Size",
"width": 65, "width": 128,
"height": 330 "height": 128
}, },
"_anchorPoint": { "_anchorPoint": {
"__type__": "cc.Vec2", "__type__": "cc.Vec2",
@@ -173,7 +173,7 @@
"a": 255 "a": 255
}, },
"_spriteFrame": { "_spriteFrame": {
"__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@5cbcb", "__uuid__": "2842dc10-d89e-4ff6-8a11-6d09bf48cf20@55402",
"__expectedType__": "cc.SpriteFrame" "__expectedType__": "cc.SpriteFrame"
}, },
"_type": 0, "_type": 0,

View File

@@ -20,6 +20,14 @@ import { BuffList } from "../common/config/BuffSet";
import { CameraFX } from "../common/CameraFX"; import { CameraFX } from "../common/CameraFX";
import { ScreenShake } from "../common/ScreenShake"; import { ScreenShake } from "../common/ScreenShake";
/** 召唤参数:数量 / XY 步进偏移 / 生成间隔,均由技能 prefab 上的 SkillView 配置 */
export interface SummonParams {
count: number;
x_offset: number;
y_offset: number;
interval: number;
}
/** /**
* ==================== 自动施法系统 ==================== * ==================== 自动施法系统 ====================
* *
@@ -93,9 +101,10 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
targetEids = this.collectFriendlyTargetEids(FacSet.HERO, undefined, true); // 获取所有英雄阵营的目标 targetEids = this.collectFriendlyTargetEids(FacSet.HERO, undefined, true); // 获取所有英雄阵营的目标
} }
const summonCount = this.resolveSummonCount(config); const summon = this.resolveSummonParams(config);
const cNum = summonCount > 1 ? 0 : Math.min(2, Math.max(0, Math.floor(overrides?.num ?? 0))); const cNum = summon.count > 1 ? 0 : Math.min(2, Math.max(0, Math.floor(overrides?.num ?? 0)));
const castTimes = Math.max(summonCount, 1 + cNum); const castTimes = Math.max(summon.count, 1 + cNum);
const castInterval = this.resolveCastInterval(summon);
// 构造一个模拟的 HeroAttrsComp 用于数值计算,只包含基础卡牌伤害计算所需的属性 // 构造一个模拟的 HeroAttrsComp 用于数值计算,只包含基础卡牌伤害计算所需的属性
const mockAttrs = new HeroAttrsComp(); const mockAttrs = new HeroAttrsComp();
@@ -149,7 +158,7 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
mLogger.log(this.debugMode, "SCastSystem", `[卡牌施法] 释放技能「${config.name}」(s_uuid:${s_uuid}) rarity:${rarity} castTimes:${castTimes} targetPos:${targetPos ? `(${targetPos.x.toFixed(0)},${targetPos.y.toFixed(0)})` : "none"} isFriendly:${isFriendly}`); mLogger.log(this.debugMode, "SCastSystem", `[卡牌施法] 释放技能「${config.name}」(s_uuid:${s_uuid}) rarity:${rarity} castTimes:${castTimes} targetPos:${targetPos ? `(${targetPos.x.toFixed(0)},${targetPos.y.toFixed(0)})` : "none"} isFriendly:${isFriendly}`);
for (let i = 0; i < castTimes; i++) { for (let i = 0; i < castTimes; i++) {
// 多发/召唤按间隔错峰执行:i=0 立即,其余延迟 i*SUMMON_INTERVAL(卡牌无施法主体,用 setTimeout 并在回调内做效期校验) // 多发/召唤按间隔错峰执行:i=0 立即,其余延迟 i*castInterval(召唤间隔由 prefab 配置)
const exec = () => { const exec = () => {
if (!smc.mission.play || smc.mission.pause) return; if (!smc.mission.play || smc.mission.pause) return;
if (isFriendly) { if (isFriendly) {
@@ -158,14 +167,14 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
if (friendlyTargets.length === 0) return; if (friendlyTargets.length === 0) return;
this.applyFriendlySkillEffects(s_uuid, rarity, config, null as any, mockAttrs, friendlyTargets, spawnPos); this.applyFriendlySkillEffects(s_uuid, rarity, config, null as any, mockAttrs, friendlyTargets, spawnPos);
} else { } else {
const enemyTargetPos = this.resolveRepeatCastTargetPos(targetPos, i); const enemyTargetPos = this.resolveRepeatCastTargetPos(targetPos, i, summon);
this.createSkillEntityForCard(s_uuid, rarity, mockAttrs, spawnPos, enemyTargetPos, i, overrides); this.createSkillEntityForCard(s_uuid, rarity, mockAttrs, spawnPos, enemyTargetPos, i, overrides, summon);
} }
}; };
if (i === 0) { if (i === 0) {
exec(); exec();
} else { } else {
setTimeout(exec, i * FightSet.SUMMON_INTERVAL * 1000); setTimeout(exec, i * castInterval * 1000);
} }
} }
// 施法后概率触发额外技能(proc_skill,原命中触发改为出手即判定;卡牌技能无视 CD、无施法主体) // 施法后概率触发额外技能(proc_skill,原命中触发改为出手即判定;卡牌技能无视 CD、无施法主体)
@@ -173,7 +182,7 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
} }
/** 专用于卡牌施放的技能实体生成 */ /** 专用于卡牌施放的技能实体生成 */
private createSkillEntityForCard(s_uuid: number, skillLv: number, mockAttrs: HeroAttrsComp, startPos: Vec3, targetPos: Vec3 | null, castIndex: number = 0, overrides?: SkillOverrides) { private createSkillEntityForCard(s_uuid: number, skillLv: number, mockAttrs: HeroAttrsComp, startPos: Vec3, targetPos: Vec3 | null, castIndex: number = 0, overrides?: SkillOverrides, summon?: SummonParams) {
const scene = smc.map.MapView.scene; const scene = smc.map.MapView.scene;
const parent = scene.entityLayer?.node?.getChildByName("SKILL"); const parent = scene.entityLayer?.node?.getChildByName("SKILL");
if (!parent || !targetPos) { if (!parent || !targetPos) {
@@ -182,7 +191,7 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
} }
const skill = ecs.getEntity<Skill>(Skill); const skill = ecs.getEntity<Skill>(Skill);
const actualStartPos = this.resolveRepeatCastStartPos(startPos, castIndex); const actualStartPos = this.resolveRepeatCastStartPos(startPos, castIndex, summon);
// 伪造一个简单的 heroView 供 Skill 初始化使用,只包含方向信息 // 伪造一个简单的 heroView 供 Skill 初始化使用,只包含方向信息
const mockView = { const mockView = {
@@ -199,31 +208,50 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
/** 查询缓存:避免每帧重复创建 matcher */ /** 查询缓存:避免每帧重复创建 matcher */
private heroMatcher: ecs.IMatcher | null = null; private heroMatcher: ecs.IMatcher | null = null;
/** 技能 prefab 召唤数量缓存:sp_name -> summon_count */ /** 技能 prefab 召唤参数缓存:sp_name -> 召唤参数(数量/XY偏移/间隔) */
private summonCountCache: Map<string, number> = new Map(); private summonParamsCache: Map<string, SummonParams> = new Map();
/** /**
* 解析单次施法的召唤数量。 * 解析单次施法的召唤参数。
* 优先级:合并后配置上的 summon_count(heroSet overrides 可覆写) > prefab 上 SkillView.summon_count。 * 数量优先级:合并后配置上的 summon_count(heroSet overrides 可覆写) > prefab 上 SkillView.summon_count;
* prefab 回退路径仅使用已缓存的资源(oops.res.get 同步命中),未加载时回退 1(单体),不引入异步等待。 * XY 偏移与间隔仅由 prefab 配置。
*/ */
private resolveSummonCount(config: SkillConfig): number { private resolveSummonParams(config: SkillConfig): SummonParams {
if (typeof config.summon_count === "number") { const fromPrefab = this.readSummonParamsFromPrefab(config.sp_name);
return Math.max(1, Math.floor(config.summon_count)); const count = typeof config.summon_count === "number"
} ? Math.max(1, Math.floor(config.summon_count))
const cached = this.summonCountCache.get(config.sp_name); : fromPrefab.count;
if (cached !== undefined) return cached; return { count, x_offset: fromPrefab.x_offset, y_offset: fromPrefab.y_offset, interval: fromPrefab.interval };
const path = `game/skill/atk/${config.sp_name}`; }
let count = 1;
/**
* 读取 prefab 上 SkillView 的召唤参数(带缓存)。
* prefab 回退路径仅使用已缓存的资源(oops.res.get 同步命中),未加载时回退默认值(单体、无偏移),不引入异步等待。
*/
private readSummonParamsFromPrefab(sp_name: string): SummonParams {
const cached = this.summonParamsCache.get(sp_name);
if (cached) return cached;
const path = `game/skill/atk/${sp_name}`;
let params: SummonParams = { count: 1, x_offset: 0, y_offset: 0, interval: 0 };
const prefab = oops.res.get(path, Prefab) as Prefab | null; const prefab = oops.res.get(path, Prefab) as Prefab | null;
if (prefab) { if (prefab) {
const view = prefab.data?.getComponent(SkillView); const view = prefab.data?.getComponent(SkillView);
if (view) { if (view) {
count = Math.max(1, Math.floor(view.summon_count)); params = {
count: Math.max(1, Math.floor(view.summon_count)),
x_offset: view.summon_x_offset,
y_offset: view.summon_y_offset,
interval: view.summon_interval,
};
} }
this.summonCountCache.set(config.sp_name, count); this.summonParamsCache.set(sp_name, params);
} }
return count; return params;
}
/** 解析错峰施法间隔:召唤技能优先 prefab 配置(>0),其余场景回退全局 SUMMON_INTERVAL */
private resolveCastInterval(summon: SummonParams): number {
return summon.count > 1 && summon.interval > 0 ? summon.interval : FightSet.SUMMON_INTERVAL;
} }
/** 获取英雄查询条件(包含属性与视图组件) */ /** 获取英雄查询条件(包含属性与视图组件) */
@@ -317,9 +345,10 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
} }
} }
const summonCount = this.resolveSummonCount(config); const summon = this.resolveSummonParams(config);
const cNum = summonCount > 1 ? 0 : Math.min(2, Math.max(0, Math.floor(overrides?.num ?? 0))); const cNum = summon.count > 1 ? 0 : Math.min(2, Math.max(0, Math.floor(overrides?.num ?? 0)));
const castTimes = Math.max(summonCount, 1 + cNum); const castTimes = Math.max(summon.count, 1 + cNum);
const castInterval = this.resolveCastInterval(summon);
let val = "" let val = ""
if (castTimes > 1) { if (castTimes > 1) {
val = "*" + castTimes.toString() val = "*" + castTimes.toString()
@@ -328,7 +357,7 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
mLogger.log(this.debugMode, "SCastSystem", `[触发施法] ${heroAttrs.hero_name}(uuid:${heroAttrs.hero_uuid}) 触发类型:${triggerType ?? "none"} 释放技能「${config.name}」(s_uuid:${s_uuid}) lv:${skillLv} ${isFriendly ? "友方" : "对敌"} castTimes:${castTimes}`); mLogger.log(this.debugMode, "SCastSystem", `[触发施法] ${heroAttrs.hero_name}(uuid:${heroAttrs.hero_uuid}) 触发类型:${triggerType ?? "none"} 释放技能「${config.name}」(s_uuid:${s_uuid}) lv:${skillLv} ${isFriendly ? "友方" : "对敌"} castTimes:${castTimes}`);
for (let i = 0; i < castTimes; i++) { for (let i = 0; i < castTimes; i++) {
if (!heroView.node || !heroView.node.isValid) return; if (!heroView.node || !heroView.node.isValid) return;
// 多发/召唤按间隔错峰执行:i=0 立即,其余延迟 i*SUMMON_INTERVAL // 多发/召唤按间隔错峰执行:i=0 立即,其余延迟 i*castInterval(召唤间隔由 prefab 配置)
heroView.scheduleOnce(() => { heroView.scheduleOnce(() => {
if (!smc.mission.play || smc.mission.pause) return; if (!smc.mission.play || smc.mission.pause) return;
if (!heroView.node || !heroView.node.isValid || heroAttrs.is_dead) return; if (!heroView.node || !heroView.node.isValid || heroAttrs.is_dead) return;
@@ -338,9 +367,9 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
this.applyFriendlySkillEffects(s_uuid, skillLv, config, heroView, heroAttrs, friendlyTargets, null); this.applyFriendlySkillEffects(s_uuid, skillLv, config, heroView, heroAttrs, friendlyTargets, null);
return; return;
} }
const enemyTargetPos = this.resolveRepeatCastTargetPos(targetPos, i); const enemyTargetPos = this.resolveRepeatCastTargetPos(targetPos, i, summon);
this.applyEnemySkillEffects(s_uuid, skillLv, config, heroView, heroAttrs, enemyTargetPos, i, overrides); this.applyEnemySkillEffects(s_uuid, skillLv, config, heroView, heroAttrs, enemyTargetPos, i, overrides, summon);
}, i * FightSet.SUMMON_INTERVAL); }, i * castInterval);
} }
// 施法后概率触发额外技能(proc_skill,原命中触发改为出手即判定,无视 CD) // 施法后概率触发额外技能(proc_skill,原命中触发改为出手即判定,无视 CD)
this.tryProcSkill(config, heroAttrs, heroView, triggerType); this.tryProcSkill(config, heroAttrs, heroView, triggerType);
@@ -399,10 +428,11 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
config = mergeSkillParams(config, overrides); config = mergeSkillParams(config, overrides);
const summonCount = this.resolveSummonCount(config); const summon = this.resolveSummonParams(config);
const isSummonSkill = summonCount > 1; const isSummonSkill = summon.count > 1;
const cNum = isSummonSkill ? 0 : Math.min(2, Math.max(0, Math.floor(overrides?.num ?? 0))); const cNum = isSummonSkill ? 0 : Math.min(2, Math.max(0, Math.floor(overrides?.num ?? 0)));
const castTimes = Math.max(summonCount, 1 + cNum); const castTimes = Math.max(summon.count, 1 + cNum);
const castInterval = this.resolveCastInterval(summon);
mLogger.log(this.debugMode, "SCastSystem", `[施法] ${heroAttrs.hero_name}(uuid:${heroAttrs.hero_uuid}) 释放技能「${config.name}」(s_uuid:${s_uuid}) lv:${skillLv} ${castPlan.isFriendly ? "友方" : "对敌"} castTimes:${castTimes}`); mLogger.log(this.debugMode, "SCastSystem", `[施法] ${heroAttrs.hero_name}(uuid:${heroAttrs.hero_uuid}) 释放技能「${config.name}」(s_uuid:${s_uuid}) lv:${skillLv} ${castPlan.isFriendly ? "友方" : "对敌"} castTimes:${castTimes}`);
@@ -444,7 +474,7 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
if (!smc.mission.play || smc.mission.pause || !smc.mission.in_fight) return; if (!smc.mission.play || smc.mission.pause || !smc.mission.in_fight) return;
if (!heroView.node || !heroView.node.isValid || heroAttrs.is_dead) return; if (!heroView.node || !heroView.node.isValid || heroAttrs.is_dead) return;
for (let i = 0; i < castTimes; i++) { for (let i = 0; i < castTimes; i++) {
// 多发/召唤按间隔错峰执行:i=0 立即,其余延迟 i*SUMMON_INTERVAL // 多发/召唤按间隔错峰执行:i=0 立即,其余延迟 i*castInterval(召唤间隔由 prefab 配置)
heroView.scheduleOnce(() => { heroView.scheduleOnce(() => {
if (!smc.mission.play || smc.mission.pause || !smc.mission.in_fight) return; if (!smc.mission.play || smc.mission.pause || !smc.mission.in_fight) return;
if (!heroView.node || !heroView.node.isValid || heroAttrs.is_dead) return; if (!heroView.node || !heroView.node.isValid || heroAttrs.is_dead) return;
@@ -454,9 +484,9 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
this.applyFriendlySkillEffects(s_uuid, skillLv, config, heroView, heroAttrs, friendlyTargets, null); this.applyFriendlySkillEffects(s_uuid, skillLv, config, heroView, heroAttrs, friendlyTargets, null);
return; return;
} }
const enemyTargetPos = this.resolveRepeatCastTargetPos(castPlan.targetPos, i); const enemyTargetPos = this.resolveRepeatCastTargetPos(castPlan.targetPos, i, summon);
this.applyEnemySkillEffects(s_uuid, skillLv, config, heroView, heroAttrs, enemyTargetPos, i, overrides); this.applyEnemySkillEffects(s_uuid, skillLv, config, heroView, heroAttrs, enemyTargetPos, i, overrides, summon);
}, i * FightSet.SUMMON_INTERVAL); }, i * castInterval);
} }
}, delay); }, delay);
@@ -497,14 +527,26 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
} }
} }
/** 多发/召唤的纵向扇形偏移档位(像素),索引按 中/上/下/上上/下下... 循环 */ /** 多发的纵向扇形偏移档位(像素),索引按 中/上/下/上上/下下... 循环;召唤技能偏移由 prefab 配置 */
private static readonly REPEAT_Y_OFFSETS: number[] = [0, 15, -15, 30, -30, 45, -45, 60, -60]; private static readonly REPEAT_Y_OFFSETS: number[] = [0, 15, -15, 30, -30, 45, -45, 60, -60];
private resolveRepeatCastTargetPos(targetPos: Vec3 | null, castIndex: number): Vec3 | null { private resolveRepeatCastTargetPos(targetPos: Vec3 | null, castIndex: number, summon?: SummonParams): Vec3 | null {
if (!targetPos) return null; if (!targetPos) return null;
const offset = SCastSystem.REPEAT_Y_OFFSETS[castIndex % SCastSystem.REPEAT_Y_OFFSETS.length]; const offset = this.resolveCastOffset(castIndex, summon);
if (offset === 0) return targetPos; if (offset.x === 0 && offset.y === 0) return targetPos;
return new Vec3(targetPos.x, targetPos.y + offset, targetPos.z); return new Vec3(targetPos.x + offset.x, targetPos.y + offset.y, targetPos.z);
}
/** 解析第 castIndex 个实体的偏移:召唤技能按 prefab 配置对称交替铺开(0, +1, -1, +2, -2... 档 × 步进值);多发回落纵向扇形档位 */
private resolveCastOffset(castIndex: number, summon?: SummonParams): { x: number; y: number } {
if (summon && summon.count > 1) {
// 对称交替:偶数索引(>=2)取负档,奇数索引取正档,使召唤物以原点为中心向两侧展开
const level = Math.ceil(castIndex / 2);
const sign = castIndex % 2 === 0 ? -1 : 1;
const factor = castIndex === 0 ? 0 : sign * level;
return { x: factor * summon.x_offset, y: factor * summon.y_offset };
}
return { x: 0, y: SCastSystem.REPEAT_Y_OFFSETS[castIndex % SCastSystem.REPEAT_Y_OFFSETS.length] };
} }
/** 构建技能尝试顺序:优先主动技能,其次普攻 */ /** 构建技能尝试顺序:优先主动技能,其次普攻 */
@@ -518,12 +560,12 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
* 创建技能实体(投射物/范围体等)。 * 创建技能实体(投射物/范围体等)。
* 仅用于对敌伤害技能的实体化表现与碰撞伤害分发。 * 仅用于对敌伤害技能的实体化表现与碰撞伤害分发。
*/ */
private createSkillEntity(s_uuid: number, skillLv: number, caster: HeroViewComp, cAttrsComp: HeroAttrsComp, targetPos: Vec3, castIndex: number = 0, overrides?: SkillOverrides) { private createSkillEntity(s_uuid: number, skillLv: number, caster: HeroViewComp, cAttrsComp: HeroAttrsComp, targetPos: Vec3, castIndex: number = 0, overrides?: SkillOverrides, summon?: SummonParams) {
if (!caster.node || !caster.node.isValid) return; if (!caster.node || !caster.node.isValid) return;
const parent = caster.node.parent; const parent = caster.node.parent;
if (!parent) return; if (!parent) return;
const skill = ecs.getEntity<Skill>(Skill); const skill = ecs.getEntity<Skill>(Skill);
const startPos = this.resolveRepeatCastStartPos(caster.node.position, castIndex); const startPos = this.resolveRepeatCastStartPos(caster.node.position, castIndex, summon);
skill.load(startPos, parent, s_uuid, targetPos.clone(), caster, cAttrsComp, skillLv, 0, overrides); skill.load(startPos, parent, s_uuid, targetPos.clone(), caster, cAttrsComp, skillLv, 0, overrides);
} }
@@ -531,17 +573,17 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
* 对敌技能效果处理。 * 对敌技能效果处理。
* 当前职责:仅处理伤害技能并创建技能实体。 * 当前职责:仅处理伤害技能并创建技能实体。
*/ */
private applyEnemySkillEffects(s_uuid: number, skillLv: number, config: SkillConfig, heroView: HeroViewComp, cAttrsComp: HeroAttrsComp, targetPos: Vec3 | null, castIndex: number = 0, overrides?: SkillOverrides) { private applyEnemySkillEffects(s_uuid: number, skillLv: number, config: SkillConfig, heroView: HeroViewComp, cAttrsComp: HeroAttrsComp, targetPos: Vec3 | null, castIndex: number = 0, overrides?: SkillOverrides, summon?: SummonParams) {
const kind = config.kind ?? SkillKind.Damage; const kind = config.kind ?? SkillKind.Damage;
if (kind !== SkillKind.Damage) return; if (kind !== SkillKind.Damage) return;
if (config.ap <= 0 || !targetPos) return; if (config.ap <= 0 || !targetPos) return;
this.createSkillEntity(s_uuid, skillLv, heroView, cAttrsComp, targetPos, castIndex, overrides); this.createSkillEntity(s_uuid, skillLv, heroView, cAttrsComp, targetPos, castIndex, overrides, summon);
} }
private resolveRepeatCastStartPos(startPos: Readonly<Vec3>, castIndex: number): Vec3 { private resolveRepeatCastStartPos(startPos: Readonly<Vec3>, castIndex: number, summon?: SummonParams): Vec3 {
const offset = SCastSystem.REPEAT_Y_OFFSETS[castIndex % SCastSystem.REPEAT_Y_OFFSETS.length]; const offset = this.resolveCastOffset(castIndex, summon);
if (offset === 0) return startPos.clone(); if (offset.x === 0 && offset.y === 0) return startPos.clone();
return new Vec3(startPos.x, startPos.y + offset, startPos.z); return new Vec3(startPos.x + offset.x, startPos.y + offset.y, startPos.z);
} }
/** /**

View File

@@ -54,6 +54,18 @@ export class SkillView extends CCComp {
@property({ type: CCInteger, slide: true, range: [1, 8, 1], tooltip: "单次施法召唤的技能实体个数" }) @property({ type: CCInteger, slide: true, range: [1, 8, 1], tooltip: "单次施法召唤的技能实体个数" })
summon_count: number = 1; summon_count: number = 1;
/** 召唤物 X 轴步进偏移(像素):summon_count>1 时以出生点/目标点为中心对称交替铺开(第1个=0,之后按 +1,-1,+2,-2... 档 × 该值,正=右) */
@property({ type: CCFloat, slide: true, range: [-200, 200, 5], tooltip: "召唤物X轴步进偏移:以中心对称交替铺开(0,+1,-1,+2,-2...档×该值)" })
summon_x_offset: number = 0;
/** 召唤物 Y 轴步进偏移(像素):summon_count>1 时与 X 同档位对称交替铺开(正=上) */
@property({ type: CCFloat, slide: true, range: [-200, 200, 5], tooltip: "召唤物Y轴步进偏移:与X同档位对称交替(0,+1,-1,+2,-2...档×该值)" })
summon_y_offset: number = 0;
/** 召唤间隔(秒):summon_count>1 时相邻两个实体的生成时间间隔(<=0 回退全局 FightSet.SUMMON_INTERVAL) */
@property({ type: CCFloat, slide: true, range: [0, 2, 0.05], tooltip: "召唤间隔(秒):相邻实体生成间隔,0=用全局默认" })
summon_interval: number = 0;
/** 抛射物高度增量(像素),贝塞尔弹道时叠加到 bezierMidHeight,控制抛物线顶点抬高 */ /** 抛射物高度增量(像素),贝塞尔弹道时叠加到 bezierMidHeight,控制抛物线顶点抬高 */
@property({ type: CCFloat, slide: true, range: [0, 500, 5], tooltip: "抛射物高度增量(叠加到贝塞尔弹道中点高度)" }) @property({ type: CCFloat, slide: true, range: [0, 500, 5], tooltip: "抛射物高度增量(叠加到贝塞尔弹道中点高度)" })
height_offset: number = 0; height_offset: number = 0;