消消方块阵换皮表情
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

329 lines
13 KiB

import { _decorator, Component, find, isValid, Node, Vec3 } from 'cc';
import { GEvent } from 'db://assets/mx/module/event/GEvent';
import { MonsterState } from './MonsterState';
import { Monster } from './Monster';
import { tetriMap } from '../../tetriMap/tetriMap';
import { jiujiuwoMap } from '../../jiujiuwoMap/jiujiuwoMap';
import { BattleType } from '../../manager/ChapterDataManager';
const { ccclass, property } = _decorator;
@ccclass('MapAgent')
export class MapAgent extends Component {
/** 全局点位分配:确保同一 wallTarget 的怪尽量分散到不同 point */
private static _wallPointPool: Map<string, { points: Node[]; order: number[]; cursor: number }> = new Map();
private static parsePointIndex(name: string, prefix: string): number | null {
if (!name) return null;
if (!name.startsWith(prefix)) return null;
const n = parseInt(name.slice(prefix.length), 10);
return Number.isFinite(n) ? n : null;
}
private getMonsterComp(): { state: MonsterState; getAtkTarget: () => void } | null {
const c1 = this.node.getComponent(Monster) as unknown as { state: MonsterState; getAtkTarget: () => void } | null;
if (c1) return c1;
// 兜底:老版本可能依赖字符串查找
return this.node.getComponent('Monster') as unknown as { state: MonsterState; getAtkTarget: () => void } | null;
}
@property
showDebug: boolean = false;
curMap: tetriMap|jiujiuwoMap = null;
/** tetriMap 终点节点路径(相对 curMap.node) */
@property
tetraEndPath: string = '目标点/wallTarget1';
/** 从 wallTarget1 下的 points(point1..point10)随机取终点 */
@property
useWallTargetPoints: boolean = true;
/** 只匹配以这个前缀开头的子节点名 */
@property
wallTargetPointPrefix: string = 'point';
/** 到达点的半径(像素),小于该值就停下攻击 */
@property
arriveRadiusToPoint: number = 18;
private _directTargetWp: Vec3 | null = null;
private _hasPickedPointTarget: boolean = false;
/** 出生点编号(用于决定走左/右 point 分组)。0 表示未知,回退用坐标判断 */
private _spawnPointId: number = 0;
/**是否正在移动 */
isMoving: boolean = false;
/**本对象是否可移动 */
CanMove: boolean = true;
/**移动速度 */
MoveSpeed: number = 100;
/**是否需要面向目标 */
NeedFaceTarget: boolean = true;
/**当前左右朝向(移动时要改变) */
private _moveDir = 1;
/**朝向更新的最小横向位移阈值,避免竖直路段抖动 */
private _faceEpsilonX = 1e-3;
/**编辑前的位置(复用向量,避免 enter/exit 编辑时 clone) */
private _editPos: Vec3 = new Vec3();
private _moveCompleteCallback: Function = null;
private _pendingPathRefresh = false;
/** move / 剩余路径距离 复用,避免每帧 new Vec3 */
private _v3Current = new Vec3();
private _v3Target = new Vec3();
private _v3Dir = new Vec3();
private _v3LastWp = new Vec3();
/**设置寻路目标 */
setTarget(target: Vec3 | Node | any, moveCompleteCallback: Function = null) {
this._moveCompleteCallback = moveCompleteCallback;
this._directTargetWp = null;
this._hasPickedPointTarget = false;
if (target instanceof Vec3) {
this._directTargetWp = target.clone();
} else if (target instanceof Node) {
this._directTargetWp = target.worldPosition.clone();
} else if (target && target.node && target.node.worldPosition) {
// 兼容旧接口传入“带 node 的对象”
this._directTargetWp = target.node.worldPosition.clone();
}
this.isMoving = !!this._directTargetWp;
}
/**由 spawner 注入出生点编号,避免对象池复用/瞬时坐标导致左右分组判断错误 */
setSpawnPointId(spawnPointId: number) {
this._spawnPointId = spawnPointId || 0;
// 重新挑选终点
this._directTargetWp = null;
this._hasPickedPointTarget = false;
}
protected onEnable(): void {
GEvent.Ins.on(GEvent.EnterEdit, this.onEnterEdit, this)
GEvent.Ins.on(GEvent.ExitEdit, this.onExitEdit, this)
}
protected onDisable(): void {
GEvent.Ins.off(GEvent.EnterEdit, this.onEnterEdit, this)
GEvent.Ins.off(GEvent.ExitEdit, this.onExitEdit, this)
}
update(deltaTime: number) {
if (!gg.game.isBattleContextActive()) return;
if(!gg.game.CurentBattle.canUpdateFrame()){
return;
}
if (this.CanMove && gg.game.CurentBattle.CanMove) {
// 如果外部没 setTarget,则尝试从 tetriMap 自动解析终点
if (!this._directTargetWp) {
this.tryAutoSetTargetFromTetraMap();
}
this.move(deltaTime);
}
}
/**尝试从curMap自动解析终点 */
tryAutoSetTargetFromTetraMap() {
// stopAndAttack() 会清空 _directTargetWp,但某些复用流程可能没重置 _hasPickedPointTarget,
// 导致这里一直 return,怪物无法重新 pick 目标点。
if (this._hasPickedPointTarget && !this._directTargetWp) {
this._hasPickedPointTarget = false;
}
if(this._hasPickedPointTarget){
return;
}
// 兼容对象池/重开局:MapAgent 可能先于 CurentBattle.curMap 初始化,
// 导致 curMap 引用为空而怪物一直拿不到终点。
if (!this.curMap || !isValid(this.curMap as any)) {
const cur = gg.game.CurentBattle?.curMap;
if (cur && isValid(cur)) {
this.curMap = cur;
}
}
const root = this.curMap?.node;
if (!root || !root.isValid) return;
// 用 find 解析路径(兼容 3.x)
let endNode: Node | null = find(this.tetraEndPath, root);
if (!endNode) endNode = find('wallTarget1', root);
if (!endNode) return;
if (!this.useWallTargetPoints) {
// 兜底:不使用 points 时,仍可直接走到 wallTarget1
this.setTarget(endNode);
return;
}
const prefix = this.wallTargetPointPrefix || 'point';
const allPts = endNode.children.filter(c => c && c.isValid && c.name && c.name.startsWith(prefix));
if (allPts.length <= 0) {
// 没配置 points 时兜底走到 wallTarget1
this.setTarget(endNode);
return;
}
// point1..point10 分组:左怪用 1..5,右怪用 6..10
// 优先按出生点编号判定(spawnPoint_1..3 走左,spawnPoint_4..6 走右),避免复用/瞬时坐标误判
// 未注入出生点时回退用“当前位置相对 wallTarget 的左右”判断
let isLeftMonster: boolean;
if (this._spawnPointId >= 1 && this._spawnPointId <= 3) {
isLeftMonster = true;
} else if (this._spawnPointId >= 4 && this._spawnPointId <= 6) {
isLeftMonster = false;
} else {
const selfX = this.node.worldPosition.x;
const endX = endNode.worldPosition.x;
isLeftMonster = selfX < endX;
}
if(gg.game.CurentBattle.BattleType == BattleType.OrangeWeaponMode_Ya){
isLeftMonster = false
}
const pts = allPts.filter(p => {
const idx = MapAgent.parsePointIndex(p.name, prefix);
if (idx == null) return false;
return isLeftMonster ? idx >= 1 && idx <= 5 : idx >= 6 && idx <= 10;
});
// 兜底:如果场景没配齐 1..10 或命名不规范,回退用全部 points
const usablePts = pts.length > 0 ? pts : allPts;
// 共享洗牌 + 轮询分配,避免大量怪随机撞到同一个点
const key = `${endNode.uuid || endNode.name}:${isLeftMonster ? 'L' : 'R'}`;
let pool = MapAgent._wallPointPool.get(key);
if (!pool || pool.points.length !== usablePts.length) {
const order = Array.from({ length: usablePts.length }, (_, i) => i);
// Fisher–Yates shuffle
for (let i = order.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
const tmp = order[i];
order[i] = order[j];
order[j] = tmp;
}
pool = { points: usablePts, order, cursor: 0 };
MapAgent._wallPointPool.set(key, pool);
} else {
// points 可能是新数组对象,但节点引用一致时替换一下
pool.points = usablePts;
}
const idx = pool.order[pool.cursor % pool.order.length];
pool.cursor = (pool.cursor + 1) % pool.order.length;
const pick = pool.points[idx];
this.setTarget(new Vec3(pick.worldPosition.x, pick.worldPosition.y, this.node.worldPosition.z));
this._hasPickedPointTarget = true;
}
private stopAndAttack() {
this._directTargetWp = null;
this.isMoving = false;
const comp = this.getMonsterComp();
if (comp) {
comp.state = MonsterState.ATTACKING;
} else {
console.log('怪物组件不存在', this.node.name);
}
this._moveCompleteCallback?.();
}
/**朝向当前移动路点移动 */
move(dt: number) {
if (!this._directTargetWp) {
this.isMoving = false;
return;
}
// 到达随机 point 后停下进入攻击
if (this.useWallTargetPoints && this._hasPickedPointTarget) {
const wp = this.node.worldPosition;
const dx = this._directTargetWp.x - wp.x;
const dy = this._directTargetWp.y - wp.y;
const r = Math.max(2, this.arriveRadiusToPoint);
if (dx * dx + dy * dy <= r * r) {
this.stopAndAttack();
return;
}
}
this.isMoving = true;
Vec3.copy(this._v3Current, this.node.worldPosition);
Vec3.copy(this._v3Target, this._directTargetWp);
// 仅在横向位移足够大时才切换朝向,竖直路点保持当前朝向,避免 x 误差导致乱跳
const dxFace = this._v3Target.x - this._v3Current.x;
if (this.NeedFaceTarget && Math.abs(dxFace) > this._faceEpsilonX) {
const Xdir = dxFace > 0 ? -1 : 1;
if (Xdir != this._moveDir) {
this._moveDir = Xdir;
let n = this.node.find("skin");
if (!n) n = this.node;
n.scale_x = Math.abs(n.scale_x) * Xdir;
}
}
Vec3.subtract(this._v3Dir, this._v3Target, this._v3Current);
const distance = this._v3Dir.length();
const moveDistance = this.MoveSpeed * gg.game.CurentBattle.TimeScale * dt;
if (distance < 1e-4) {
this.node.setWorldPosition(this._v3Target);
this.stopAndAttack();
return;
}
if (moveDistance > distance) {
this.node.setWorldPosition(this._v3Target);
this.stopAndAttack();
return;
}
Vec3.normalize(this._v3Dir, this._v3Dir);
Vec3.scaleAndAdd(this._v3Current, this._v3Current, this._v3Dir, moveDistance);
this.node.setWorldPosition(this._v3Current);
}
onEnterEdit() {
Vec3.copy(this._editPos, this.node.worldPosition);
}
onExitEdit(save: boolean) {
if (!save) {
this.node.setWorldPosition(this._editPos);
}
this.getMonsterComp()?.getAtkTarget();
}
/**获取剩余路径距离(简化:当前点到目标点的直线距离,供排序/估算使用) */
getRemainPathDistance(): number {
if (!this.node?.isValid) return 0;
if (!this._directTargetWp) {
this.tryAutoSetTargetFromTetraMap();
}
if (!this._directTargetWp) return 0;
return Vec3.distance(this.node.worldPosition, this._directTargetWp);
}
/**传入武器节点,返回当前怪物到武器的距离 */
getNearestMonster(weaponNode: Node): number {
if (!weaponNode || !isValid(weaponNode) || !this.node?.isValid) {
return Infinity;
}
const monsterComp = this.node.getComponent(Monster);
if (monsterComp?.getIsDead()) {
return Infinity;
}
return Vec3.distance(weaponNode.worldPosition, this.node.worldPosition);
}
/**
* 进入大厅/主界面时清空静态出怪点轮询表。
* Key 含 wallTarget 节点 uuid,多局战斗会不断累积,需在回 home 时释放。
*/
public static clearStaticWallPointPool(): void {
MapAgent._wallPointPool.clear();
}
}