refactor: 重构击退机制,将概率改为距离增量
1. 调整近战默认攻击距离从150改为100 2. 将击退相关属性从概率改为击退距离增量,同步更新所有配置、UI显示和技能描述 3. 移除旧的击退概率判定逻辑,改为直接应用击退距离增量 4. 优化受击击退的处理流程,统一参数传递
This commit is contained in:
@@ -122,7 +122,7 @@ export class Hero extends ecs.Entity {
|
||||
model.grow_type = hero.grow_type;
|
||||
model.role = hero.role;
|
||||
model.fac = FacSet.HERO;
|
||||
// 攻击距离:配置优先;未配置按攻击定位取默认值(近战150 / 中程300 / 远程450)
|
||||
// 攻击距离:配置优先;未配置按攻击定位取默认值(近战100 / 中程300 / 远程450)
|
||||
model.dis = hero.dis ?? HeroDisVal[hero.type as HType.Melee | HType.Mid | HType.Long];
|
||||
model.posIndex = posIndex;
|
||||
if (posIndex >= 0) {
|
||||
|
||||
@@ -175,11 +175,6 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpda
|
||||
TAttrsComp.atked_count++;
|
||||
this.checkAndTriggerAtkedSkills(TAttrsComp, targetView);
|
||||
|
||||
// 受击者产生击退效果
|
||||
// if (damage > 0 && targetView) {
|
||||
// targetView.back();
|
||||
// }
|
||||
|
||||
mLogger.log(this.debugMode, 'HeroAtkSystem', ` 英雄${TAttrsComp.hero_name} (uuid: ${TAttrsComp.hero_uuid}) 受到 eid:${casterEid} 的 伤害 ${damage},${isCrit ? "暴击" : "普通"}攻击,技能ID ${damageEvent.s_uuid}`);
|
||||
|
||||
// 冰冻判定
|
||||
@@ -190,13 +185,14 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpda
|
||||
const stunChance = (damageEvent.Attrs[Attrs.stun_chance] || 0) - TAttrsComp.getFinalStunRes();
|
||||
const isStun = !TAttrsComp.isStun() && this.checkChance(stunChance);
|
||||
|
||||
// 击退判定
|
||||
const knockbackChance = (damageEvent.Attrs[Attrs.knockback_chance] || 0) - (TAttrsComp.knockback_res || 0);
|
||||
const isKnockback = this.checkChance(knockbackChance);
|
||||
// 击退:受击默认后退,击退属性不再作为概率,而是转换为后退距离的增量
|
||||
// 击退增量 = 施法者击退强化(原击退概率)- 被攻击者击退抗性 + 击退距离强化
|
||||
const knockbackBonus = (damageEvent.Attrs[Attrs.knockback_chance] || 0) - (TAttrsComp.getFinalKnockbackRes() || 0);
|
||||
const knockbackExtra = Math.max(0, knockbackBonus + (damageEvent.Attrs[Attrs.knockback_distance] || 0));
|
||||
|
||||
// ✅ 触发视图层表现(伤害数字、受击动画、冰冻、击晕、击退)
|
||||
if (targetView) {
|
||||
targetView.do_atked(damage, isCrit, damageEvent.s_uuid, false);
|
||||
targetView.do_atked(damage, isCrit, damageEvent.s_uuid, knockbackExtra);
|
||||
targetView.playEnd(skillConf.endAnm);
|
||||
if (isFrost) {
|
||||
TAttrsComp.toFrost();
|
||||
@@ -212,9 +208,6 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpda
|
||||
smc.vmdata.scores.stun_count++;
|
||||
}
|
||||
}
|
||||
if (isKnockback) {
|
||||
targetView.back(damageEvent.Attrs[Attrs.knockback_distance] || 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -395,7 +388,7 @@ export class HeroAtkSystem extends ecs.ComblockSystem implements ecs.ISystemUpda
|
||||
* 用于所有概率相关的判定:
|
||||
* - 暴击率判定
|
||||
* - 闪避率判定
|
||||
* - 击退概率判定
|
||||
* - 冰冻/击晕概率判定
|
||||
* - 其他特殊效果概率
|
||||
*
|
||||
* @param rate 概率值(0-100的百分比)
|
||||
|
||||
@@ -82,9 +82,9 @@ export class HeroAttrsComp extends ecs.Comp {
|
||||
freeze_res: number = 0; // 冰冻抗性
|
||||
stun_chance: number = 0; // 击晕概率
|
||||
stun_res: number = 0; // 击晕抗性
|
||||
knockback_chance: number = 0; // 击退概率
|
||||
knockback_chance: number = 0; // 击退强化(原击退概率,现转换为击退距离增量)
|
||||
knockback_distance: number = 0; // 击退距离强化
|
||||
knockback_res: number = 0; // 击退抗性
|
||||
knockback_res: number = 0; // 击退抗性(削减击退距离增量)
|
||||
crit_damage: number = 0; // 额外暴击伤害
|
||||
puncture_chance: number = 0; // 穿透概率
|
||||
puncture_dmg_bonus: number = 0; // 穿透后伤害增量(百分点,乘算加成)
|
||||
@@ -449,7 +449,7 @@ export class HeroAttrsComp extends ecs.Comp {
|
||||
return base + mods.flatSum + mods.pctSum;
|
||||
}
|
||||
|
||||
/** 最终击退概率 = 基础击退 + 限时修饰(加法模型) */
|
||||
/** 最终击退强化 = 基础击退强化 + 限时修饰(加法模型,转换为击退距离增量) */
|
||||
public getFinalKnockbackChance(): number {
|
||||
const mods = this.aggregateTimedMods(Attrs.knockback_chance);
|
||||
return this.knockback_chance + mods.flatSum + mods.pctSum;
|
||||
|
||||
@@ -525,7 +525,8 @@ export class HeroViewComp extends CCComp {
|
||||
})
|
||||
.start();
|
||||
}
|
||||
do_atked(damage: number, isCrit: boolean, s_uuid: number, isBack: boolean = false) {
|
||||
/** 受击表现(伤害数字、受击动画、击退) */
|
||||
do_atked(damage: number, isCrit: boolean, s_uuid: number, knockbackExtra: number = 0) {
|
||||
// 受到攻击时更新最后更新时间
|
||||
this.activateTopBar();
|
||||
this.lastBarUpdateTime = Date.now() / 1000;
|
||||
@@ -542,7 +543,7 @@ export class HeroViewComp extends CCComp {
|
||||
// 视图层表现
|
||||
let SConf = SkillSet[s_uuid]
|
||||
const hitAnm = SConf?.DAnm || "atked";
|
||||
if (isBack) this.back()
|
||||
this.back(knockbackExtra)
|
||||
this.in_atked(hitAnm, this.model.fac == FacSet.HERO ? 1 : -1);
|
||||
this.showDamage(damage, isCrit);
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ export class Monster extends ecs.Entity {
|
||||
model.speed = hero.speed ?? 800;
|
||||
model.type = hero.type;
|
||||
model.fac = FacSet.MON;
|
||||
// 攻击距离:配置优先;未配置按攻击定位取默认值(近战150 / 中程300 / 远程450)
|
||||
// 攻击距离:配置优先;未配置按攻击定位取默认值(近战100 / 中程300 / 远程450)
|
||||
model.dis = hero.dis ?? HeroDisVal[hero.type as HType.Melee | HType.Mid | HType.Long];
|
||||
model.posIndex = laneIndex;
|
||||
if (laneIndex >= 0) {
|
||||
|
||||
@@ -136,14 +136,9 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
||||
return dist <= attackRange;
|
||||
}
|
||||
|
||||
/** 同阵营横向最小间距,防止停止移动时完全重叠 */
|
||||
private static readonly ALLY_MIN_GAP_X = 10;
|
||||
/** 挤位时的蠕动幅度(像素) */
|
||||
private static readonly JOSTLE_AMPLITUDE = 2;
|
||||
|
||||
/**
|
||||
* 战斗位移:向最近敌人推进,进入攻击范围后停止。
|
||||
* 推进无上限限制,可穿越队友;停止移动后受同阵营最小间距约束。
|
||||
* 推进无上限限制,但击退/位移受阵营后退边界保护。
|
||||
*/
|
||||
private processCombatLogic(_e: ecs.Entity, move: MoveComp, view: HeroViewComp, model: HeroAttrsComp, enemy: HeroViewComp) {
|
||||
const selfX = view.node.position.x;
|
||||
@@ -160,10 +155,8 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
||||
if (view.status === "move") {
|
||||
view.status_change("idle");
|
||||
}
|
||||
/** 停止时若与队友重叠,则向后微调保持最小间距,并加入蠕动避免死板 */
|
||||
this.adjustSpacingWhenIdle(_e, view, move, model);
|
||||
} else {
|
||||
/** 未接触:向敌人方向推进(可穿越队友,但同点时后来者稍停) */
|
||||
/** 未接触:向敌人方向推进 */
|
||||
model.is_atking = false;
|
||||
const dir = enemyX > selfX ? 1 : -1;
|
||||
move.direction = dir;
|
||||
@@ -171,9 +164,6 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
||||
const delta = speed * this.dt * dir;
|
||||
let newX = selfX + delta;
|
||||
|
||||
/** 推进时若与队友同点,后来者稍停避让 */
|
||||
newX = this.adjustSpacingWhileMoving(_e, view, move, model, newX, dir);
|
||||
|
||||
/** 后退边界钳制:防止被击退/位移推出屏幕 */
|
||||
newX = this.clampByFacBoundary(newX, model.fac);
|
||||
|
||||
@@ -184,93 +174,6 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 推进时的间距避让。
|
||||
* Why: 允许穿越队友,但若与队友完全同点(重叠),后来者稍停一拍,
|
||||
* 避免视觉上的"穿模"过于生硬,营造"挤过去"的自然感。
|
||||
* @returns 调整后的目标 X(被阻挡时返回 selfX,即本帧不移动)
|
||||
*/
|
||||
private adjustSpacingWhileMoving(self: ecs.Entity, view: HeroViewComp, selfMove: MoveComp, model: HeroAttrsComp, targetX: number, _dir: number): number {
|
||||
const selfX = view.node.position.x;
|
||||
const selfY = view.node.position.y;
|
||||
|
||||
let blocked = false;
|
||||
ecs.query(this.getHeroViewMatcher()).forEach(e => {
|
||||
if (e === self) return;
|
||||
const attrs = e.get(HeroAttrsComp);
|
||||
const v = e.get(HeroViewComp);
|
||||
const mv = e.get(MoveComp);
|
||||
if (!attrs || !v?.node || !mv) return;
|
||||
if (attrs.is_dead || attrs.is_reviving) return;
|
||||
if (attrs.fac !== model.fac) return;
|
||||
if (Math.abs(v.node.position.y - selfY) > 30) return;
|
||||
|
||||
const otherX = v.node.position.x;
|
||||
/** 同点判定:间距小于最小间距的一半视为完全重叠 */
|
||||
if (Math.abs(selfX - otherX) < MoveSystem.ALLY_MIN_GAP_X * 0.5) {
|
||||
/** spawnOrder 大者(后来者)被阻挡 */
|
||||
if (selfMove.spawnOrder > mv.spawnOrder) {
|
||||
blocked = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return blocked ? selfX : targetX;
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止移动时的间距微调 + 蠕动效果。
|
||||
* Why: 允许推进时穿越队友,但进入攻击范围停下后若与队友重叠,
|
||||
* 则向后微调保持 ALLY_MIN_GAP_X 间距,避免视觉完全重叠。
|
||||
* 同时加入基于时间的随机蠕动,营造"挤到前方攻击"的生动感。
|
||||
* Y 轴不变,仅约束 X。
|
||||
* 优先级:spawnOrder 小者(先出生)不动,大者向后让位。
|
||||
*/
|
||||
private adjustSpacingWhenIdle(self: ecs.Entity, view: HeroViewComp, selfMove: MoveComp, model: HeroAttrsComp): void {
|
||||
const selfX = view.node.position.x;
|
||||
const selfY = view.node.position.y;
|
||||
let adjustX = selfX;
|
||||
let isCrowded = false;
|
||||
|
||||
ecs.query(this.getHeroViewMatcher()).forEach(e => {
|
||||
if (e === self) return;
|
||||
const attrs = e.get(HeroAttrsComp);
|
||||
const v = e.get(HeroViewComp);
|
||||
const mv = e.get(MoveComp);
|
||||
if (!attrs || !v?.node || !mv) return;
|
||||
if (attrs.is_dead || attrs.is_reviving) return;
|
||||
if (attrs.fac !== model.fac) return;
|
||||
/** 只约束同排(Y 差在阈值内)的队友 */
|
||||
if (Math.abs(v.node.position.y - selfY) > 30) return;
|
||||
|
||||
const otherX = v.node.position.x;
|
||||
const gap = Math.abs(adjustX - otherX);
|
||||
if (gap >= MoveSystem.ALLY_MIN_GAP_X) return;
|
||||
|
||||
isCrowded = true;
|
||||
/** spawnOrder 小者不动,大者向后让位 */
|
||||
if (selfMove.spawnOrder > mv.spawnOrder) {
|
||||
/** 自己靠后:向远离对方的方向后退到最小间距处 */
|
||||
const dir = adjustX >= otherX ? 1 : -1;
|
||||
adjustX = otherX + dir * MoveSystem.ALLY_MIN_GAP_X;
|
||||
}
|
||||
});
|
||||
|
||||
/** 拥挤时加入蠕动:基于时间+出生序的伪随机偏移,避免多个单位同步抖动 */
|
||||
if (isCrowded) {
|
||||
const time = Date.now() / 1000;
|
||||
const jostlePhase = time * 3 + selfMove.spawnOrder * 0.7; // 不同单位错开相位
|
||||
const jostleOffset = Math.sin(jostlePhase) * MoveSystem.JOSTLE_AMPLITUDE;
|
||||
adjustX += jostleOffset;
|
||||
}
|
||||
|
||||
if (Math.abs(adjustX - selfX) >= 0.01) {
|
||||
/** 后退边界钳制 */
|
||||
adjustX = this.clampByFacBoundary(adjustX, model.fac);
|
||||
view.node.setPosition(adjustX, view.node.position.y, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 按阵营裁剪 X 坐标,防止被击退/位移推出屏幕。
|
||||
* HERO 后退不越过 BoxSet.LETF_END;MON 后退不越过 BoxSet.RIGHT_END。
|
||||
|
||||
Reference in New Issue
Block a user