Compare commits
3 Commits
72feccbcd8
...
9b35482b3c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b35482b3c | ||
|
|
20e9b1d484 | ||
|
|
86363f50b0 |
@@ -414,7 +414,7 @@
|
|||||||
"a": 255
|
"a": 255
|
||||||
},
|
},
|
||||||
"_spriteFrame": {
|
"_spriteFrame": {
|
||||||
"__uuid__": "031877cb-0f3d-4e92-bc5d-e492a0d95a08@f4f28",
|
"__uuid__": "031877cb-0f3d-4e92-bc5d-e492a0d95a08@d17da",
|
||||||
"__expectedType__": "cc.SpriteFrame"
|
"__expectedType__": "cc.SpriteFrame"
|
||||||
},
|
},
|
||||||
"_type": 1,
|
"_type": 1,
|
||||||
@@ -429,7 +429,10 @@
|
|||||||
"_fillRange": 0,
|
"_fillRange": 0,
|
||||||
"_isTrimmedMode": true,
|
"_isTrimmedMode": true,
|
||||||
"_useGrayscale": false,
|
"_useGrayscale": false,
|
||||||
"_atlas": null,
|
"_atlas": {
|
||||||
|
"__uuid__": "031877cb-0f3d-4e92-bc5d-e492a0d95a08",
|
||||||
|
"__expectedType__": "cc.SpriteAtlas"
|
||||||
|
},
|
||||||
"_id": ""
|
"_id": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ec
|
|||||||
import { Initialize } from "../initialize/Initialize";
|
import { Initialize } from "../initialize/Initialize";
|
||||||
import { GameMap } from "../map/GameMap";
|
import { GameMap } from "../map/GameMap";
|
||||||
import { oops } from "../../../../extensions/oops-plugin-framework/assets/core/Oops";
|
import { oops } from "../../../../extensions/oops-plugin-framework/assets/core/Oops";
|
||||||
import { WxCloudApi } from "../wx_clound_client_api/WxCloudApi";
|
|
||||||
import { GameEvent } from "./config/GameEvent";
|
import { GameEvent } from "./config/GameEvent";
|
||||||
import { GameScoreStats } from "./config/HeroAttrs";
|
import { GameScoreStats } from "./config/HeroAttrs";
|
||||||
import { mLogger } from "./Logger";
|
import { mLogger } from "./Logger";
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ export const SkillSet: Record<number, SkillConfig> = {
|
|||||||
6001: {
|
6001: {
|
||||||
uuid:6001,name:t("skill_name_6001"),sp_name:"atk",icon:"1026",TGroup:TGroup.Enemy,readyAnm:"",endAnm:"",act:"atk",
|
uuid:6001,name:t("skill_name_6001"),sp_name:"atk",icon:"1026",TGroup:TGroup.Enemy,readyAnm:"",endAnm:"",act:"atk",
|
||||||
DTType:DTType.single,ap:100,hit_count:1,hitcd:0.2,speed:720,with:0,ready:0.2,EAnm:0,DAnm:"",IType:IType.Melee,
|
DTType:DTType.single,ap:100,hit_count:1,hitcd:0.2,speed:720,with:0,ready:0.2,EAnm:0,DAnm:"",IType:IType.Melee,
|
||||||
RType:RType.linear,EType:EType.collision,buffs:[],info:t("skill_info_6001", 1, 100),
|
RType:RType.bezier,EType:EType.collision,buffs:[],info:t("skill_info_6001", 1, 100),
|
||||||
},
|
},
|
||||||
6002: {
|
6002: {
|
||||||
uuid:6002,name:t("skill_name_6002"),sp_name:"ball_fire",icon:"1126",TGroup:TGroup.Enemy,readyAnm:"",endAnm:"",act:"atk",
|
uuid:6002,name:t("skill_name_6002"),sp_name:"ball_fire",icon:"1126",TGroup:TGroup.Enemy,readyAnm:"",endAnm:"",act:"atk",
|
||||||
@@ -208,7 +208,7 @@ export const SkillSet: Record<number, SkillConfig> = {
|
|||||||
6004: {
|
6004: {
|
||||||
uuid:6004,name:t("skill_name_6004"),sp_name:"ball_zi",icon:"1126",TGroup:TGroup.Enemy,readyAnm:"",endAnm:"",act:"atk",
|
uuid:6004,name:t("skill_name_6004"),sp_name:"ball_zi",icon:"1126",TGroup:TGroup.Enemy,readyAnm:"",endAnm:"",act:"atk",
|
||||||
DTType:DTType.single,ap:100,hit_count:1,hitcd:0.3,speed:720,with:90,ready:0.2,EAnm:0,DAnm:"",IType:IType.remote,
|
DTType:DTType.single,ap:100,hit_count:1,hitcd:0.3,speed:720,with:90,ready:0.2,EAnm:0,DAnm:"",IType:IType.remote,
|
||||||
RType:RType.linear,EType:EType.collision,buffs:[],info:t("skill_info_6004", 1, 100),
|
RType:RType.bezier,EType:EType.collision,buffs:[],info:t("skill_info_6004", 1, 100),
|
||||||
},
|
},
|
||||||
6005: {
|
6005: {
|
||||||
uuid:6005,name:t("skill_name_6005"),sp_name:"arrow",icon:"1135",TGroup:TGroup.Enemy,readyAnm:"",endAnm:"",act:"atk",
|
uuid:6005,name:t("skill_name_6005"),sp_name:"arrow",icon:"1135",TGroup:TGroup.Enemy,readyAnm:"",endAnm:"",act:"atk",
|
||||||
@@ -248,7 +248,7 @@ export const SkillSet: Record<number, SkillConfig> = {
|
|||||||
6104: {
|
6104: {
|
||||||
uuid:6104,name:t("skill_name_6104"),sp_name:"arrow_big_yellow",icon:"1135",TGroup:TGroup.Enemy,readyAnm:"yellow",endAnm:"",act:"max",
|
uuid:6104,name:t("skill_name_6104"),sp_name:"arrow_big_yellow",icon:"1135",TGroup:TGroup.Enemy,readyAnm:"yellow",endAnm:"",act:"max",
|
||||||
DTType:DTType.single,crt:20,ap:100,hit_count:6,hitcd:0.2,speed:720,with:0,ready:0.2,EAnm:0,DAnm:"",IType:IType.remote,
|
DTType:DTType.single,crt:20,ap:100,hit_count:6,hitcd:0.2,speed:720,with:0,ready:0.2,EAnm:0,DAnm:"",IType:IType.remote,
|
||||||
RType:RType.linear,EType:EType.collision,buffs:[],info:t("skill_info_6104", 6, 100),
|
RType:RType.bezier,EType:EType.collision,buffs:[],info:t("skill_info_6104", 6, 100),
|
||||||
},
|
},
|
||||||
6105: {
|
6105: {
|
||||||
uuid:6105,name:t("skill_name_6105"),sp_name:"atk_fire",icon:"1173",TGroup:TGroup.Enemy,readyAnm:"blues",endAnm:"",act:"max",
|
uuid:6105,name:t("skill_name_6105"),sp_name:"atk_fire",icon:"1173",TGroup:TGroup.Enemy,readyAnm:"blues",endAnm:"",act:"max",
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ export const FormationPointX = {
|
|||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const HeroDisVal: Record<HType.Melee | HType.Mid | HType.Long, number> = {
|
export const HeroDisVal: Record<HType.Melee | HType.Mid | HType.Long, number> = {
|
||||||
[HType.Melee]: 720,
|
[HType.Melee]: 120,
|
||||||
[HType.Mid]: 720,
|
[HType.Mid]: 720,
|
||||||
[HType.Long]: 720,
|
[HType.Long]: 720,
|
||||||
}
|
}
|
||||||
@@ -207,7 +207,7 @@ export const HeroInfo: Record<number, heroInfo> = {
|
|||||||
6001:{uuid:6001,name:t("mon_name_6001"),path:"mo1", fac:FacSet.MON,cards_lv:1,lv:1,type:HType.Melee,hp:120,ap:12,speed:100,
|
6001:{uuid:6001,name:t("mon_name_6001"),path:"mo1", fac:FacSet.MON,cards_lv:1,lv:1,type:HType.Melee,hp:120,ap:12,speed:100,
|
||||||
skills:{6001:{uuid:6001,lv:1,cd:0.65,ccd:0}},info:""},
|
skills:{6001:{uuid:6001,lv:1,cd:0.65,ccd:0}},info:""},
|
||||||
6002:{uuid:6002,name:t("mon_name_6002"),path:"mo3", fac:FacSet.MON,cards_lv:1,lv:1,type:HType.Melee,hp:120,ap:12,speed:100,
|
6002:{uuid:6002,name:t("mon_name_6002"),path:"mo3", fac:FacSet.MON,cards_lv:1,lv:1,type:HType.Melee,hp:120,ap:12,speed:100,
|
||||||
skills:{6001:{uuid:6001,lv:1,cd:0.65,ccd:0},6004:{uuid:6004,lv:1,cd:10,ccd:0}},info:""},
|
skills:{6001:{uuid:6001,lv:1,cd:0.65,ccd:0}},info:""},
|
||||||
6003:{uuid:6003,name:t("mon_name_6003"),path:"mo4", fac:FacSet.MON,cards_lv:1,lv:1,type:HType.Melee,hp:350,ap:30,speed:100,
|
6003:{uuid:6003,name:t("mon_name_6003"),path:"mo4", fac:FacSet.MON,cards_lv:1,lv:1,type:HType.Melee,hp:350,ap:30,speed:100,
|
||||||
skills:{6001:{uuid:6001,lv:1,cd:2,ccd:0}},info:""},
|
skills:{6001:{uuid:6001,lv:1,cd:2,ccd:0}},info:""},
|
||||||
// 4. 远程
|
// 4. 远程
|
||||||
@@ -217,7 +217,7 @@ export const HeroInfo: Record<number, heroInfo> = {
|
|||||||
skills:{6001:{uuid:6203,lv:1,cd:1.5,ccd:0}},info:""},
|
skills:{6001:{uuid:6203,lv:1,cd:1.5,ccd:0}},info:""},
|
||||||
// 6. 精英/BOSS型
|
// 6. 精英/BOSS型
|
||||||
6006:{uuid:6006,name:t("mon_name_6006"),path:"mo6", fac:FacSet.MON,cards_lv:1,lv:1,type:HType.Melee,hp:1500,ap:20,speed:100,
|
6006:{uuid:6006,name:t("mon_name_6006"),path:"mo6", fac:FacSet.MON,cards_lv:1,lv:1,type:HType.Melee,hp:1500,ap:20,speed:100,
|
||||||
skills:{6002:{uuid:6002,lv:1,cd:2,ccd:0},6004:{uuid:6004,lv:1,cd:10,ccd:0}},info:""},
|
skills:{6002:{uuid:6002,lv:1,cd:2,ccd:0}},info:""},
|
||||||
//============== 亡灵系列 ===============
|
//============== 亡灵系列 ===============
|
||||||
// 近战型
|
// 近战型
|
||||||
6101:{uuid:6101,name:t("mon_name_6101"),path:"mud1", fac:FacSet.MON,cards_lv:1,lv:1,type:HType.Melee,hp:120,ap:12,speed:100,
|
6101:{uuid:6101,name:t("mon_name_6101"),path:"mud1", fac:FacSet.MON,cards_lv:1,lv:1,type:HType.Melee,hp:120,ap:12,speed:100,
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ export class Monster extends ecs.Entity {
|
|||||||
* 2) 初始化表现、属性、技能与阵营
|
* 2) 初始化表现、属性、技能与阵营
|
||||||
* 3) 播放下落入场并在落地后启用碰撞与移动
|
* 3) 播放下落入场并在落地后启用碰撞与移动
|
||||||
*/
|
*/
|
||||||
load(pos: Vec3 = Vec3.ZERO,scale:number = 1,uuid:number=1001, is_boss:boolean=false, dropToY:number = pos.y,mon_lv:number=1, flyLaneIndex:number = 0) {
|
load(pos: Vec3 = Vec3.ZERO,scale:number = 1,uuid:number=1001, is_boss:boolean=false, dropToY:number = pos.y,mon_lv:number=1, laneIndex:number = 0) {
|
||||||
// 怪物默认朝左,表现缩放固定为负向
|
// 怪物默认朝左,表现缩放固定为负向
|
||||||
scale=-1
|
scale=-1
|
||||||
// 当前怪物尺寸固定,保留变量便于后续扩展
|
// 当前怪物尺寸固定,保留变量便于后续扩展
|
||||||
@@ -220,19 +220,8 @@ export class Monster extends ecs.Entity {
|
|||||||
if (!node || !node.isValid) return;
|
if (!node || !node.isValid) return;
|
||||||
// 落地后锁定最终位置,切换到落地完成状态
|
// 落地后锁定最终位置,切换到落地完成状态
|
||||||
node.setPosition(pos.x, dropToY, 0);
|
node.setPosition(pos.x, dropToY, 0);
|
||||||
// 如果是飞行怪,可以保持空中状态,这里依然调用 down
|
|
||||||
view.playEnd("down");
|
view.playEnd("down");
|
||||||
|
|
||||||
// 飞行怪加一个轻微的上下浮动动效
|
|
||||||
if (flyLaneIndex > 0) {
|
|
||||||
tween(node)
|
|
||||||
.by(1.5, { position: v3(0, 15, 0) }, { easing: "sineOut" })
|
|
||||||
.by(1.5, { position: v3(0, -15, 0) }, { easing: "sineIn" })
|
|
||||||
.union()
|
|
||||||
.repeatForever()
|
|
||||||
.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
move.moving = true;
|
move.moving = true;
|
||||||
// 落地后启用怪物碰撞分组
|
// 落地后启用怪物碰撞分组
|
||||||
if (collider) {
|
if (collider) {
|
||||||
|
|||||||
@@ -30,10 +30,6 @@ export class MonMoveComp extends ecs.Comp {
|
|||||||
|
|
||||||
@ecs.register('MonMoveSystem')
|
@ecs.register('MonMoveSystem')
|
||||||
export class MonMoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate {
|
export class MonMoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate {
|
||||||
/** 近战判定射程 */
|
|
||||||
private readonly meleeAttackRange = 250;
|
|
||||||
/** 远程判定射程 */
|
|
||||||
private readonly longAttackRange = 600;
|
|
||||||
/** 渲染层级重排节流,避免每帧排序 */
|
/** 渲染层级重排节流,避免每帧排序 */
|
||||||
private readonly renderSortInterval = 0.05;
|
private readonly renderSortInterval = 0.05;
|
||||||
private lastRenderSortAt = 0;
|
private lastRenderSortAt = 0;
|
||||||
@@ -128,8 +124,8 @@ export class MonMoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpda
|
|||||||
private isEnemyInAttackRange(model: HeroAttrsComp, selfX: number, enemyX: number): boolean {
|
private isEnemyInAttackRange(model: HeroAttrsComp, selfX: number, enemyX: number): boolean {
|
||||||
const dist = Math.abs(selfX - enemyX);
|
const dist = Math.abs(selfX - enemyX);
|
||||||
const rangeType = model.type as HType.Melee | HType.Mid | HType.Long;
|
const rangeType = model.type as HType.Melee | HType.Mid | HType.Long;
|
||||||
if (rangeType === HType.Melee) return dist <= this.meleeAttackRange;
|
const attackRange = HeroDisVal[rangeType];
|
||||||
return dist <= this.longAttackRange;
|
return dist <= attackRange;
|
||||||
}
|
}
|
||||||
|
|
||||||
private processCombatLogic(e: ecs.Entity, move: MonMoveComp, view: HeroViewComp, model: HeroAttrsComp, enemy: HeroViewComp) {
|
private processCombatLogic(e: ecs.Entity, move: MonMoveComp, view: HeroViewComp, model: HeroAttrsComp, enemy: HeroViewComp) {
|
||||||
@@ -139,10 +135,8 @@ export class MonMoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpda
|
|||||||
const inRange = this.isEnemyInAttackRange(model, selfX, enemyX);
|
const inRange = this.isEnemyInAttackRange(model, selfX, enemyX);
|
||||||
|
|
||||||
// 接触判定距离,只有接触英雄才停止移动
|
// 接触判定距离,只有接触英雄才停止移动
|
||||||
// 飞行怪忽略接触碰撞,不被阻挡
|
|
||||||
const isFly = move.baseY > 50;
|
|
||||||
const touchDistance = 50;
|
const touchDistance = 50;
|
||||||
const isTouching = !isFly && (dist <= touchDistance);
|
const isTouching = dist <= touchDistance;
|
||||||
|
|
||||||
// 攻击判定
|
// 攻击判定
|
||||||
if (inRange) {
|
if (inRange) {
|
||||||
|
|||||||
@@ -48,10 +48,6 @@ interface MoveFacConfig {
|
|||||||
|
|
||||||
@ecs.register('MoveSystem')
|
@ecs.register('MoveSystem')
|
||||||
export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate {
|
export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate {
|
||||||
/** 近战判定射程 */
|
|
||||||
private readonly meleeAttackRange = 250;
|
|
||||||
/** 远程判定射程 */
|
|
||||||
private readonly longAttackRange = 600;
|
|
||||||
private readonly heroFrontAnchorX = -100;
|
private readonly heroFrontAnchorX = -100;
|
||||||
private readonly monFrontAnchorX = 0;
|
private readonly monFrontAnchorX = 0;
|
||||||
/** 常规同阵营横向最小间距(英雄) */
|
/** 常规同阵营横向最小间距(英雄) */
|
||||||
@@ -65,7 +61,7 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
|||||||
private lastRenderSortAt = 0;
|
private lastRenderSortAt = 0;
|
||||||
private heroMoveMatcher: ecs.IMatcher | null = null;
|
private heroMoveMatcher: ecs.IMatcher | null = null;
|
||||||
private heroViewMatcher: ecs.IMatcher | null = null;
|
private heroViewMatcher: ecs.IMatcher | null = null;
|
||||||
private readonly renderEntries: { node: Node; bossPriority: number; frontScore: number; spawnOrder: number; eid: number }[] = [];
|
private readonly renderEntries: { node: Node; bossPriority: number; frontScore: number; spawnOrder: number; eid: number; laneScore: number }[] = [];
|
||||||
private renderEntryCount = 0;
|
private renderEntryCount = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -166,8 +162,8 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
|||||||
private isEnemyInAttackRange(model: HeroAttrsComp, selfX: number, enemyX: number): boolean {
|
private isEnemyInAttackRange(model: HeroAttrsComp, selfX: number, enemyX: number): boolean {
|
||||||
const dist = Math.abs(selfX - enemyX);
|
const dist = Math.abs(selfX - enemyX);
|
||||||
const rangeType = model.type as HType.Melee | HType.Mid | HType.Long;
|
const rangeType = model.type as HType.Melee | HType.Mid | HType.Long;
|
||||||
if (rangeType === HType.Melee) return dist <= this.meleeAttackRange;
|
const attackRange = HeroDisVal[rangeType];
|
||||||
return dist <= this.longAttackRange;
|
return dist <= attackRange;
|
||||||
}
|
}
|
||||||
|
|
||||||
private processCombatLogic(e: ecs.Entity, move: MoveComp, view: HeroViewComp, model: HeroAttrsComp, enemy: HeroViewComp) {
|
private processCombatLogic(e: ecs.Entity, move: MoveComp, view: HeroViewComp, model: HeroAttrsComp, enemy: HeroViewComp) {
|
||||||
@@ -391,18 +387,21 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const isFly = actorView.node.position.y > 50;
|
// 按 Y 轴计算渲染层级权重(Y 越小,说明越靠近屏幕下方,应该渲染在越前面)
|
||||||
// 飞行怪在最前,其次是地面的 X 坐标
|
// 之前的 isFly 逻辑已被移除,统一按 Y 轴处理三路渲染
|
||||||
const frontScore = isFly ? 999999 - actorView.node.position.x : (attrs.fac === FacSet.HERO ? actorView.node.position.x : -actorView.node.position.x);
|
const laneScore = -actorView.node.position.y;
|
||||||
|
// X 轴权重:站在前排的(交战处)优先渲染
|
||||||
|
const frontScore = attrs.fac === FacSet.HERO ? actorView.node.position.x : -actorView.node.position.x;
|
||||||
|
|
||||||
const entryIndex = this.renderEntryCount;
|
const entryIndex = this.renderEntryCount;
|
||||||
let entry = this.renderEntries[entryIndex];
|
let entry = this.renderEntries[entryIndex];
|
||||||
if (!entry) {
|
if (!entry) {
|
||||||
entry = { node: actorView.node, bossPriority: 0, frontScore: 0, spawnOrder: 0, eid: 0 };
|
entry = { node: actorView.node, bossPriority: 0, frontScore: 0, spawnOrder: 0, eid: 0, laneScore: 0 };
|
||||||
this.renderEntries.push(entry);
|
this.renderEntries.push(entry);
|
||||||
}
|
}
|
||||||
entry.node = actorView.node;
|
entry.node = actorView.node;
|
||||||
entry.bossPriority = attrs.is_boss ? 1 : 0;
|
entry.bossPriority = attrs.is_boss ? 1 : 0;
|
||||||
|
entry.laneScore = laneScore;
|
||||||
entry.frontScore = frontScore;
|
entry.frontScore = frontScore;
|
||||||
entry.spawnOrder = spawnOrder;
|
entry.spawnOrder = spawnOrder;
|
||||||
entry.eid = e.eid;
|
entry.eid = e.eid;
|
||||||
@@ -410,9 +409,11 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
|||||||
});
|
});
|
||||||
this.renderEntries.length = this.renderEntryCount;
|
this.renderEntries.length = this.renderEntryCount;
|
||||||
|
|
||||||
/** 定时重排同层节点:Boss 优先级 -> 前后位置 -> 出生序 -> eid */
|
/** 定时重排同层节点:Boss 优先级 -> Y 轴路次 -> 前后位置 -> 出生序 -> eid */
|
||||||
this.renderEntries.sort((a, b) => {
|
this.renderEntries.sort((a, b) => {
|
||||||
if (a.bossPriority !== b.bossPriority) return a.bossPriority - b.bossPriority;
|
if (a.bossPriority !== b.bossPriority) return a.bossPriority - b.bossPriority;
|
||||||
|
// Y轴靠下的(laneScore 较大)应该在后面,从而渲染在上面
|
||||||
|
if (Math.abs(a.laneScore - b.laneScore) > 10) return a.laneScore - b.laneScore;
|
||||||
if (a.frontScore !== b.frontScore) return a.frontScore - b.frontScore;
|
if (a.frontScore !== b.frontScore) return a.frontScore - b.frontScore;
|
||||||
if (a.spawnOrder !== b.spawnOrder) return a.spawnOrder - b.spawnOrder;
|
if (a.spawnOrder !== b.spawnOrder) return a.spawnOrder - b.spawnOrder;
|
||||||
return a.eid - b.eid;
|
return a.eid - b.eid;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { HeroViewComp } from "./HeroViewComp";
|
|||||||
import { DTType, RType, SkillConfig, SkillKind, SkillSet, SkillUpList, TGroup } from "../common/config/SkillSet";
|
import { DTType, RType, SkillConfig, SkillKind, SkillSet, SkillUpList, TGroup } from "../common/config/SkillSet";
|
||||||
import { Skill } from "../skill/Skill";
|
import { Skill } from "../skill/Skill";
|
||||||
import { smc } from "../common/SingletonModuleComp";
|
import { smc } from "../common/SingletonModuleComp";
|
||||||
import { HeroInfo, HType } from "../common/config/heroSet";
|
import { HeroDisVal, HeroInfo, HType } from "../common/config/heroSet";
|
||||||
import { Attrs } from "../common/config/HeroAttrs";
|
import { Attrs } from "../common/config/HeroAttrs";
|
||||||
import { BoxSet, FacSet, FightSet } from "../common/config/GameSet";
|
import { BoxSet, FacSet, FightSet } from "../common/config/GameSet";
|
||||||
import { oops } from "db://oops-framework/core/Oops";
|
import { oops } from "db://oops-framework/core/Oops";
|
||||||
@@ -140,8 +140,6 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
|||||||
}
|
}
|
||||||
/** 空施法计划:用于“当前无可施法技能”时的统一返回 */
|
/** 空施法计划:用于“当前无可施法技能”时的统一返回 */
|
||||||
private readonly emptyCastPlan = { skillId: 0, skillLv: 1, isFriendly: false, targetPos: null as Vec3 | null, targetEids: [] as number[] };
|
private readonly emptyCastPlan = { skillId: 0, skillLv: 1, isFriendly: false, targetPos: null as Vec3 | null, targetEids: [] as number[] };
|
||||||
/** 近战英雄默认施法射程 */
|
|
||||||
private readonly meleeCastRange = 64;
|
|
||||||
/** 查询缓存:避免每帧重复创建 matcher */
|
/** 查询缓存:避免每帧重复创建 matcher */
|
||||||
private heroMatcher: ecs.IMatcher | null = null;
|
private heroMatcher: ecs.IMatcher | null = null;
|
||||||
|
|
||||||
@@ -543,12 +541,16 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
|||||||
/**
|
/**
|
||||||
* 在施法距离内查找最近敌人。
|
* 在施法距离内查找最近敌人。
|
||||||
* 用于单体技能与基础目标参考。
|
* 用于单体技能与基础目标参考。
|
||||||
|
* 考虑三路设计:同路(Y差较小)优先,如果同路没有目标再考虑跨路
|
||||||
*/
|
*/
|
||||||
private findNearestEnemyInRange(heroAttrs: HeroAttrsComp, heroView: HeroViewComp, maxRange: number): HeroViewComp | null {
|
private findNearestEnemyInRange(heroAttrs: HeroAttrsComp, heroView: HeroViewComp, maxRange: number): HeroViewComp | null {
|
||||||
if (!heroView.node) return null;
|
if (!heroView.node) return null;
|
||||||
const currentX = heroView.node.position.x;
|
const currentX = heroView.node.position.x;
|
||||||
|
const currentY = heroView.node.position.y;
|
||||||
let nearest: HeroViewComp | null = null;
|
let nearest: HeroViewComp | null = null;
|
||||||
let minDist = Infinity;
|
let minDist = Infinity;
|
||||||
|
let foundSameLane = false;
|
||||||
|
|
||||||
ecs.query(this.getHeroMatcher()).forEach(entity => {
|
ecs.query(this.getHeroMatcher()).forEach(entity => {
|
||||||
const attrs = entity.get(HeroAttrsComp);
|
const attrs = entity.get(HeroAttrsComp);
|
||||||
const view = entity.get(HeroViewComp);
|
const view = entity.get(HeroViewComp);
|
||||||
@@ -556,10 +558,26 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
|||||||
if (attrs.fac === heroAttrs.fac) return;
|
if (attrs.fac === heroAttrs.fac) return;
|
||||||
if (attrs.is_dead || attrs.is_reviving) return;
|
if (attrs.is_dead || attrs.is_reviving) return;
|
||||||
if (this.isOutOfBattleBounds(view.node.position.x)) return;
|
if (this.isOutOfBattleBounds(view.node.position.x)) return;
|
||||||
const dist = Math.abs(currentX - view.node.position.x);
|
|
||||||
if (dist > maxRange) return;
|
const distX = Math.abs(currentX - view.node.position.x);
|
||||||
if (dist >= minDist) return;
|
if (distX > maxRange) return;
|
||||||
minDist = dist;
|
|
||||||
|
const isSameLane = Math.abs(currentY - view.node.position.y) < 30; // 30为容差
|
||||||
|
|
||||||
|
// 如果之前找到了同路目标,且当前不是同路,直接跳过
|
||||||
|
if (foundSameLane && !isSameLane) return;
|
||||||
|
|
||||||
|
// 如果当前是同路,且之前没找到同路,则强制替换(同路优先)
|
||||||
|
if (isSameLane && !foundSameLane) {
|
||||||
|
foundSameLane = true;
|
||||||
|
minDist = distX;
|
||||||
|
nearest = view;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 同等路况下比较距离
|
||||||
|
if (distX >= minDist) return;
|
||||||
|
minDist = distX;
|
||||||
nearest = view;
|
nearest = view;
|
||||||
});
|
});
|
||||||
return nearest;
|
return nearest;
|
||||||
@@ -568,13 +586,17 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
|||||||
/**
|
/**
|
||||||
* 在施法距离内查找“最前排”敌人。
|
* 在施法距离内查找“最前排”敌人。
|
||||||
* 依据施法者面向方向选择 x 轴上更前的目标。
|
* 依据施法者面向方向选择 x 轴上更前的目标。
|
||||||
|
* 考虑三路设计:同路优先
|
||||||
*/
|
*/
|
||||||
private findFrontEnemyInRange(heroAttrs: HeroAttrsComp, heroView: HeroViewComp, maxRange: number, nearestEnemy: HeroViewComp): HeroViewComp | null {
|
private findFrontEnemyInRange(heroAttrs: HeroAttrsComp, heroView: HeroViewComp, maxRange: number, nearestEnemy: HeroViewComp): HeroViewComp | null {
|
||||||
if (!heroView.node || !nearestEnemy.node) return null;
|
if (!heroView.node || !nearestEnemy.node) return null;
|
||||||
const currentX = heroView.node.position.x;
|
const currentX = heroView.node.position.x;
|
||||||
|
const currentY = heroView.node.position.y;
|
||||||
const direction = nearestEnemy.node.position.x >= currentX ? 1 : -1;
|
const direction = nearestEnemy.node.position.x >= currentX ? 1 : -1;
|
||||||
let frontEnemy: HeroViewComp | null = null;
|
let frontEnemy: HeroViewComp | null = null;
|
||||||
let edgeX = direction > 0 ? Infinity : -Infinity;
|
let edgeX = direction > 0 ? Infinity : -Infinity;
|
||||||
|
let foundSameLane = false;
|
||||||
|
|
||||||
ecs.query(this.getHeroMatcher()).forEach(entity => {
|
ecs.query(this.getHeroMatcher()).forEach(entity => {
|
||||||
const attrs = entity.get(HeroAttrsComp);
|
const attrs = entity.get(HeroAttrsComp);
|
||||||
const view = entity.get(HeroViewComp);
|
const view = entity.get(HeroViewComp);
|
||||||
@@ -583,8 +605,20 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
|||||||
if (attrs.is_dead || attrs.is_reviving) return;
|
if (attrs.is_dead || attrs.is_reviving) return;
|
||||||
const enemyX = view.node.position.x;
|
const enemyX = view.node.position.x;
|
||||||
if (this.isOutOfBattleBounds(enemyX)) return;
|
if (this.isOutOfBattleBounds(enemyX)) return;
|
||||||
|
|
||||||
const dist = Math.abs(currentX - enemyX);
|
const dist = Math.abs(currentX - enemyX);
|
||||||
if (dist > maxRange) return;
|
if (dist > maxRange) return;
|
||||||
|
|
||||||
|
const isSameLane = Math.abs(currentY - view.node.position.y) < 30;
|
||||||
|
if (foundSameLane && !isSameLane) return;
|
||||||
|
|
||||||
|
if (isSameLane && !foundSameLane) {
|
||||||
|
foundSameLane = true;
|
||||||
|
edgeX = enemyX;
|
||||||
|
frontEnemy = view;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (direction > 0) {
|
if (direction > 0) {
|
||||||
if (enemyX >= edgeX) return;
|
if (enemyX >= edgeX) return;
|
||||||
edgeX = enemyX;
|
edgeX = enemyX;
|
||||||
@@ -618,9 +652,8 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
|||||||
private resolveMaxCastRange(heroAttrs: HeroAttrsComp, type: HType): number {
|
private resolveMaxCastRange(heroAttrs: HeroAttrsComp, type: HType): number {
|
||||||
const cached = heroAttrs.getCachedMaxSkillDistance();
|
const cached = heroAttrs.getCachedMaxSkillDistance();
|
||||||
if (cached > 0) return cached;
|
if (cached > 0) return cached;
|
||||||
if (type === HType.Long) return 720;
|
const rangeType = type as HType.Melee | HType.Mid | HType.Long;
|
||||||
if (type === HType.Mid) return 360;
|
return HeroDisVal[rangeType];
|
||||||
return this.meleeCastRange;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 生成沿目标方向的施法目标坐标 */
|
/** 生成沿目标方向的施法目标坐标 */
|
||||||
|
|||||||
@@ -58,6 +58,12 @@ export class MissionHeroCompComp extends CCComp {
|
|||||||
private static readonly HERO_SPAWN_START_MELEE_X = -320
|
private static readonly HERO_SPAWN_START_MELEE_X = -320
|
||||||
/** 远程(含中程)英雄起始出生 X 坐标 */
|
/** 远程(含中程)英雄起始出生 X 坐标 */
|
||||||
private static readonly HERO_SPAWN_START_RANGED_X = -320
|
private static readonly HERO_SPAWN_START_RANGED_X = -320
|
||||||
|
/** 三路高度偏移(上路, 中路, 下路) */
|
||||||
|
private static readonly HERO_LANE_Y_OFFSETS = [100, 0, -100]
|
||||||
|
/** 每路前排容量 */
|
||||||
|
private static readonly HERO_LANE_CAP = 3
|
||||||
|
/** 同路内 X 间距 */
|
||||||
|
private static readonly HERO_GAP_X = 100
|
||||||
|
|
||||||
// ======================== 运行时属性 ========================
|
// ======================== 运行时属性 ========================
|
||||||
|
|
||||||
@@ -124,7 +130,8 @@ export class MissionHeroCompComp extends CCComp {
|
|||||||
if (model && view) {
|
if (model && view) {
|
||||||
if (model.is_dead) {
|
if (model.is_dead) {
|
||||||
view.alive();
|
view.alive();
|
||||||
const landingPos = this.resolveHeroLandingPos(model.hero_uuid);
|
const { lane, indexInLane } = this.pickLaneForHero(model.hero_uuid, [hero.eid]);
|
||||||
|
const landingPos = this.resolveHeroLandingPos(model.hero_uuid, lane, indexInLane);
|
||||||
// 不再直接设置位置,而是播放下落入场动画
|
// 不再直接设置位置,而是播放下落入场动画
|
||||||
// 计算出出生点(空中)
|
// 计算出出生点(空中)
|
||||||
const spawnPos: Vec3 = v3(landingPos.x, landingPos.y + MissionHeroCompComp.HERO_DROP_HEIGHT, 0);
|
const spawnPos: Vec3 = v3(landingPos.x, landingPos.y + MissionHeroCompComp.HERO_DROP_HEIGHT, 0);
|
||||||
@@ -159,6 +166,53 @@ export class MissionHeroCompComp extends CCComp {
|
|||||||
|
|
||||||
// ======================== 英雄生成 ========================
|
// ======================== 英雄生成 ========================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 动态分配英雄上场的路和排位(优先中路 -> 上路 -> 下路)
|
||||||
|
* @param uuid 英雄 UUID
|
||||||
|
* @param excludeEids 排除计算的实体ID数组(避免复活或合成时把自己算成占据的位置)
|
||||||
|
*/
|
||||||
|
private pickLaneForHero(uuid: number, excludeEids: number[] = []): { lane: number; indexInLane: number } {
|
||||||
|
const heroes = this.getAllHeroes().filter(h => {
|
||||||
|
const m = h.get(HeroAttrsComp);
|
||||||
|
return m && !m.is_dead && !excludeEids.includes(h.eid);
|
||||||
|
});
|
||||||
|
|
||||||
|
const counts = [0, 0, 0];
|
||||||
|
const baseY = HeroPos[0].pos.y;
|
||||||
|
|
||||||
|
for (const h of heroes) {
|
||||||
|
const view = h.get(HeroViewComp);
|
||||||
|
if (!view || !view.node) continue;
|
||||||
|
|
||||||
|
let y = view.node.position.y;
|
||||||
|
// 处理正在掉落状态的英雄(y 值偏高)
|
||||||
|
if (y > baseY + 150) {
|
||||||
|
y -= MissionHeroCompComp.HERO_DROP_HEIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
let nearest = 1, best = Infinity;
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
const d = Math.abs(y - (baseY + MissionHeroCompComp.HERO_LANE_Y_OFFSETS[i]));
|
||||||
|
if (d < best) {
|
||||||
|
best = d;
|
||||||
|
nearest = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
counts[nearest]++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 优先中路(1) -> 上路(0) -> 下路(2)
|
||||||
|
const priority = [1, 0, 2];
|
||||||
|
for (const lane of priority) {
|
||||||
|
if (counts[lane] < MissionHeroCompComp.HERO_LANE_CAP) {
|
||||||
|
return { lane, indexInLane: counts[lane] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 溢出:仍放中路,沿 X 继续排
|
||||||
|
return { lane: 1, indexInLane: counts[1] };
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成一个英雄 ECS 实体:
|
* 生成一个英雄 ECS 实体:
|
||||||
* - 计算出生点(空中)和落点(地面)。
|
* - 计算出生点(空中)和落点(地面)。
|
||||||
@@ -173,7 +227,8 @@ export class MissionHeroCompComp extends CCComp {
|
|||||||
console.log("addHero uuid:",uuid)
|
console.log("addHero uuid:",uuid)
|
||||||
let hero = ecs.getEntity<Hero>(Hero);
|
let hero = ecs.getEntity<Hero>(Hero);
|
||||||
let scale = 1
|
let scale = 1
|
||||||
const landingPos = this.resolveHeroLandingPos(uuid);
|
const { lane, indexInLane } = this.pickLaneForHero(uuid);
|
||||||
|
const landingPos = this.resolveHeroLandingPos(uuid, lane, indexInLane);
|
||||||
let spawnPos:Vec3 = v3(landingPos.x, landingPos.y + MissionHeroCompComp.HERO_DROP_HEIGHT, 0);
|
let spawnPos:Vec3 = v3(landingPos.x, landingPos.y + MissionHeroCompComp.HERO_DROP_HEIGHT, 0);
|
||||||
hero.load(spawnPos,scale,uuid,landingPos.y,hero_lv,pool_lv);
|
hero.load(spawnPos,scale,uuid,landingPos.y,hero_lv,pool_lv);
|
||||||
|
|
||||||
@@ -194,13 +249,15 @@ export class MissionHeroCompComp extends CCComp {
|
|||||||
* Y 坐标来自 HeroPos 配置,X 坐标根据英雄类型(近战/远程)决定。
|
* Y 坐标来自 HeroPos 配置,X 坐标根据英雄类型(近战/远程)决定。
|
||||||
*
|
*
|
||||||
* @param uuid 英雄 UUID
|
* @param uuid 英雄 UUID
|
||||||
|
* @param lane 分配到的路 (0: 上, 1: 中, 2: 下)
|
||||||
|
* @param indexInLane 该路排位
|
||||||
* @returns 落点 Vec3
|
* @returns 落点 Vec3
|
||||||
*/
|
*/
|
||||||
private resolveHeroLandingPos(uuid: number): Vec3 {
|
private resolveHeroLandingPos(uuid: number, lane: number, indexInLane: number): Vec3 {
|
||||||
const hero_pos = 0;
|
const hero_pos = 0;
|
||||||
const baseY = HeroPos[hero_pos].pos.y;
|
const baseY = HeroPos[hero_pos].pos.y + MissionHeroCompComp.HERO_LANE_Y_OFFSETS[lane];
|
||||||
const startX = this.resolveSpawnStartX(uuid);
|
const startX = this.resolveSpawnStartX(uuid);
|
||||||
return v3(startX, baseY, 0);
|
return v3(startX + indexInLane * MissionHeroCompComp.HERO_GAP_X, baseY, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -223,18 +280,43 @@ export class MissionHeroCompComp extends CCComp {
|
|||||||
* @param pool_lv 卡池等级
|
* @param pool_lv 卡池等级
|
||||||
* @param ap 聚合后攻击力
|
* @param ap 聚合后攻击力
|
||||||
* @param hp_max 聚合后最大生命值
|
* @param hp_max 聚合后最大生命值
|
||||||
|
* @param targetLane 指定生成路
|
||||||
|
* @param targetIndex 指定该路排位
|
||||||
* @returns 实际生成的英雄等级
|
* @returns 实际生成的英雄等级
|
||||||
*/
|
*/
|
||||||
private addMergedHero(uuid:number, hero_lv:number, pool_lv:number, ap:number, hp_max:number): number {
|
private addMergedHero(uuid:number, hero_lv:number, pool_lv:number, ap:number, hp_max:number, targetLane?: number, targetIndex?: number): number {
|
||||||
const hero = this.addHero(uuid, hero_lv, pool_lv);
|
console.log("addMergedHero uuid:",uuid)
|
||||||
|
let hero = ecs.getEntity<Hero>(Hero);
|
||||||
|
let scale = 1
|
||||||
|
|
||||||
|
// 如果未指定路,则按普通添加英雄处理
|
||||||
|
let lane = targetLane;
|
||||||
|
let indexInLane = targetIndex;
|
||||||
|
if (lane === undefined || indexInLane === undefined) {
|
||||||
|
const res = this.pickLaneForHero(uuid);
|
||||||
|
lane = res.lane;
|
||||||
|
indexInLane = res.indexInLane;
|
||||||
|
}
|
||||||
|
|
||||||
|
const landingPos = this.resolveHeroLandingPos(uuid, lane, indexInLane);
|
||||||
|
let spawnPos:Vec3 = v3(landingPos.x, landingPos.y + MissionHeroCompComp.HERO_DROP_HEIGHT, 0);
|
||||||
|
hero.load(spawnPos,scale,uuid,landingPos.y,hero_lv,pool_lv);
|
||||||
|
|
||||||
|
// 召唤完成后,派发事件以更新英雄面板
|
||||||
const model = hero.get(HeroAttrsComp);
|
const model = hero.get(HeroAttrsComp);
|
||||||
if (!model) return hero_lv;
|
if (model) {
|
||||||
model.ap = Math.max(0, ap);
|
model.ap = Math.max(0, ap);
|
||||||
model.hp_max = Math.max(1, hp_max);
|
model.hp_max = Math.max(1, hp_max);
|
||||||
model.hp = model.hp_max;
|
model.hp = model.hp_max;
|
||||||
model.dirty_hp = true;
|
model.dirty_hp = true;
|
||||||
|
oops.message.dispatchEvent(GameEvent.MasterCalled, {
|
||||||
|
eid: hero.eid,
|
||||||
|
model: model
|
||||||
|
});
|
||||||
return model.lv;
|
return model.lv;
|
||||||
}
|
}
|
||||||
|
return hero_lv;
|
||||||
|
}
|
||||||
|
|
||||||
// ======================== 英雄查询 ========================
|
// ======================== 英雄查询 ========================
|
||||||
|
|
||||||
@@ -448,19 +530,24 @@ export class MissionHeroCompComp extends CCComp {
|
|||||||
// 聚合属性
|
// 聚合属性
|
||||||
let sumAp = 0;
|
let sumAp = 0;
|
||||||
let sumHpMax = 0;
|
let sumHpMax = 0;
|
||||||
|
const mergeEids = [];
|
||||||
for (let i = 0; i < mergeHeroes.length; i++) {
|
for (let i = 0; i < mergeHeroes.length; i++) {
|
||||||
const model = mergeHeroes[i].get(HeroAttrsComp);
|
const model = mergeHeroes[i].get(HeroAttrsComp);
|
||||||
|
mergeEids.push(mergeHeroes[i].eid);
|
||||||
if (!model) continue;
|
if (!model) continue;
|
||||||
sumAp += model.ap;
|
sumAp += model.ap;
|
||||||
sumHpMax += model.hp_max;
|
sumHpMax += model.hp_max;
|
||||||
}
|
}
|
||||||
// 计算出生点
|
|
||||||
const landingPos = this.resolveHeroLandingPos(uuid);
|
// 计算目标出生点(提前排除素材英雄所占的位置)
|
||||||
|
const { lane, indexInLane } = this.pickLaneForHero(uuid, mergeEids);
|
||||||
|
const landingPos = this.resolveHeroLandingPos(uuid, lane, indexInLane);
|
||||||
const spawnPos:Vec3 = v3(landingPos.x, landingPos.y + MissionHeroCompComp.HERO_DROP_HEIGHT, 0);
|
const spawnPos:Vec3 = v3(landingPos.x, landingPos.y + MissionHeroCompComp.HERO_DROP_HEIGHT, 0);
|
||||||
|
|
||||||
// 汇聚 → 特效 → 生成
|
// 汇聚 → 特效 → 生成
|
||||||
await this.mergeDestroyAtBirth(mergeHeroes, spawnPos);
|
await this.mergeDestroyAtBirth(mergeHeroes, spawnPos);
|
||||||
await this.playMergeBoomFx(spawnPos);
|
await this.playMergeBoomFx(spawnPos);
|
||||||
return this.addMergedHero(uuid, Math.min(this.merge_max_lv, hero_lv + 1), pool_lv, sumAp, sumHpMax);
|
return this.addMergedHero(uuid, Math.min(this.merge_max_lv, hero_lv + 1), pool_lv, sumAp, sumHpMax, lane, indexInLane);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -57,8 +57,8 @@ export class MissionMonCompComp extends CCComp {
|
|||||||
private static readonly MON_SPAWN_GAP_X = 50;
|
private static readonly MON_SPAWN_GAP_X = 50;
|
||||||
/** 怪物出生掉落高度 */
|
/** 怪物出生掉落高度 */
|
||||||
private static readonly MON_DROP_HEIGHT = 280;
|
private static readonly MON_DROP_HEIGHT = 280;
|
||||||
/** 飞行层高度偏移(地面, 空中1, 空中2) */
|
/** 三路高度偏移(上路, 中路, 下路) */
|
||||||
private static readonly FLY_LANE_Y_OFFSETS = [0, 120, 240];
|
private static readonly LANE_Y_OFFSETS = [100, 0, -100];
|
||||||
|
|
||||||
// ======================== 编辑器属性 ========================
|
// ======================== 编辑器属性 ========================
|
||||||
|
|
||||||
@@ -172,9 +172,22 @@ export class MissionMonCompComp extends CCComp {
|
|||||||
|
|
||||||
// ======================== 插队刷怪 ========================
|
// ======================== 插队刷怪 ========================
|
||||||
|
|
||||||
|
/** 选取当前排数最少的路(均衡分配) */
|
||||||
|
private pickBalancedLane(): number {
|
||||||
|
let min = this.laneIndices[0];
|
||||||
|
let lane = 0;
|
||||||
|
for (let i = 1; i < 3; i++) {
|
||||||
|
if (this.laneIndices[i] < min) {
|
||||||
|
min = this.laneIndices[i];
|
||||||
|
lane = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lane;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理插队刷怪队列(每 0.15 秒尝试消费一个):
|
* 处理插队刷怪队列(每 0.15 秒尝试消费一个):
|
||||||
* 1. 根据飞行层排号。
|
* 1. 根据指定路或均衡分路。
|
||||||
* 2. 找到后从队列中移除并生成怪物。
|
* 2. 找到后从队列中移除并生成怪物。
|
||||||
*/
|
*/
|
||||||
private updateSpecialQueue(dt: number) {
|
private updateSpecialQueue(dt: number) {
|
||||||
@@ -190,7 +203,7 @@ export class MissionMonCompComp extends CCComp {
|
|||||||
(MonList[MonType.FlyBoss] && MonList[MonType.FlyBoss].includes(item.uuid));
|
(MonList[MonType.FlyBoss] && MonList[MonType.FlyBoss].includes(item.uuid));
|
||||||
|
|
||||||
const upType = this.getRandomUpType();
|
const upType = this.getRandomUpType();
|
||||||
const lane = item.flyLane >= 0 && item.flyLane <= 2 ? item.flyLane : 0;
|
const lane = item.flyLane !== undefined && item.flyLane >= 0 && item.flyLane <= 2 ? item.flyLane : this.pickBalancedLane();
|
||||||
|
|
||||||
this.addMonsterAt(lane, this.laneIndices[lane], item.uuid, isBoss, upType, Math.max(1, Number(item.level ?? 1)));
|
this.addMonsterAt(lane, this.laneIndices[lane], item.uuid, isBoss, upType, Math.max(1, Number(item.level ?? 1)));
|
||||||
this.laneIndices[lane]++;
|
this.laneIndices[lane]++;
|
||||||
@@ -305,24 +318,27 @@ export class MissionMonCompComp extends CCComp {
|
|||||||
// 解析配置
|
// 解析配置
|
||||||
for (const slot of config) {
|
for (const slot of config) {
|
||||||
const isBoss = slot.type === MonType.MeleeBoss || slot.type === MonType.LongBoss || slot.type === MonType.FlyBoss;
|
const isBoss = slot.type === MonType.MeleeBoss || slot.type === MonType.LongBoss || slot.type === MonType.FlyBoss;
|
||||||
// 判断分配到的飞行层
|
|
||||||
let lane: number = slot.flyLane !== undefined ? slot.flyLane : 0;
|
|
||||||
if (slot.type === MonType.Fly || slot.type === MonType.FlyBoss) {
|
|
||||||
lane = slot.flyLane !== undefined ? slot.flyLane : 1; // 飞行怪默认在第一层
|
|
||||||
}
|
|
||||||
lane = Math.max(0, Math.min(2, lane)); // 约束在 0,1,2
|
|
||||||
|
|
||||||
for (let i = 0; i < slot.count; i++) {
|
for (let i = 0; i < slot.count; i++) {
|
||||||
const uuid = this.getRandomUuidByType(slot.type);
|
const uuid = this.getRandomUuidByType(slot.type);
|
||||||
const upType = this.getRandomUpType();
|
const upType = this.getRandomUpType();
|
||||||
|
// 优先使用配置的 lane,否则均衡分配
|
||||||
|
let lane = slot.flyLane !== undefined ? slot.flyLane : this.pickBalancedLane();
|
||||||
|
lane = Math.max(0, Math.min(2, lane));
|
||||||
|
|
||||||
const req = { uuid, isBoss, upType, monLv: wave, lane };
|
const req = { uuid, isBoss, upType, monLv: wave, lane };
|
||||||
allMons.push(req);
|
allMons.push(req);
|
||||||
|
// 提前累加 laneIndices,以便本波内的均衡分配能正确计算
|
||||||
|
this.laneIndices[lane]++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.waveTargetCount = allMons.length;
|
this.waveTargetCount = allMons.length;
|
||||||
this.waveSpawnedCount = 0;
|
this.waveSpawnedCount = 0;
|
||||||
|
|
||||||
|
// 由于上面循环中已经累加了 laneIndices,这里需要重置,以便下面真正生成时再累加(或者直接利用 allMons 生成)
|
||||||
|
this.laneIndices = [0, 0, 0];
|
||||||
|
|
||||||
// 4. 立即生成本波所有怪物
|
// 4. 立即生成本波所有怪物
|
||||||
for (const req of allMons) {
|
for (const req of allMons) {
|
||||||
this.addMonsterAt(req.lane, this.laneIndices[req.lane], req.uuid, req.isBoss, req.upType, req.monLv);
|
this.addMonsterAt(req.lane, this.laneIndices[req.lane], req.uuid, req.isBoss, req.upType, req.monLv);
|
||||||
@@ -335,8 +351,8 @@ export class MissionMonCompComp extends CCComp {
|
|||||||
/**
|
/**
|
||||||
* 在指定层级、指定索引处生成一个怪物:
|
* 在指定层级、指定索引处生成一个怪物:
|
||||||
*
|
*
|
||||||
* @param laneIndex 飞行层索引 (0, 1, 2)
|
* @param laneIndex 三路索引 (0 上, 1 中, 2 下)
|
||||||
* @param monIndex 该层级的第几个怪 (0, 1, 2...)
|
* @param monIndex 该路级的第几个怪 (0, 1, 2...)
|
||||||
* @param uuid 怪物 UUID
|
* @param uuid 怪物 UUID
|
||||||
* @param isBoss 是否为 Boss
|
* @param isBoss 是否为 Boss
|
||||||
* @param upType 属性成长类型
|
* @param upType 属性成长类型
|
||||||
@@ -355,7 +371,7 @@ export class MissionMonCompComp extends CCComp {
|
|||||||
|
|
||||||
// 计算坐标
|
// 计算坐标
|
||||||
const spawnX = MissionMonCompComp.MON_SPAWN_START_X + monIndex * MissionMonCompComp.MON_SPAWN_GAP_X;
|
const spawnX = MissionMonCompComp.MON_SPAWN_START_X + monIndex * MissionMonCompComp.MON_SPAWN_GAP_X;
|
||||||
const landingY = BoxSet.GAME_LINE + MissionMonCompComp.FLY_LANE_Y_OFFSETS[laneIndex] + (isBoss ? 6 : 0);
|
const landingY = BoxSet.GAME_LINE + MissionMonCompComp.LANE_Y_OFFSETS[laneIndex] + (isBoss ? 6 : 0);
|
||||||
const spawnPos: Vec3 = v3(spawnX, landingY + MissionMonCompComp.MON_DROP_HEIGHT, 0);
|
const spawnPos: Vec3 = v3(spawnX, landingY + MissionMonCompComp.MON_DROP_HEIGHT, 0);
|
||||||
this.globalSpawnOrder = (this.globalSpawnOrder + 1) % 999;
|
this.globalSpawnOrder = (this.globalSpawnOrder + 1) % 999;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user