| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424 |
- import { _decorator, Component, Node, Vec3, RigidBody2D, Collider2D, Contact2DType, IPhysics2DContact, Sprite, find, SpriteFrame } from 'cc';
- import { EnemyAttackEffectManager, AttackEffectType } from '../EnemyAttackAni/EnemyAttackEffectManager';
- import { BundleLoader } from '../../Core/BundleLoader';
- import { Audio } from '../../AudioManager/AudioManager';
- import EventBus, { GameEvents } from '../../Core/EventBus';
- const { ccclass, property } = _decorator;
- /**
- * 敌人抛掷物实例组件
- * 负责处理单个抛掷物的移动、碰撞和伤害
- * 此组件应该挂载到抛掷物预制体上
- */
- @ccclass('EnemyProjectileInstance')
- export class EnemyProjectileInstance extends Component {
-
- // 抛掷物属性
- private damage: number = 10;
- private speed: number = 100;
- private direction: Vec3 = new Vec3(0, -1, 0); // 默认向下
- private projectileType: string = 'arrow';
-
- // 组件引用
- private rigidBody: RigidBody2D = null;
- private collider: Collider2D = null;
- private sprite: Sprite = null;
-
- // 生命周期
- private maxLifetime: number = 5.0; // 最大存活时间
- private currentLifetime: number = 0;
-
- // 弧线弹道相关属性
- private useArcTrajectory: boolean = true; // 是否使用弧线弹道
- private arcDir: Vec3 = null; // 当前方向
- private arcTargetDir: Vec3 = null; // 目标方向
- private targetWallPosition: Vec3 = null; // 目标墙体位置
- private rotateSpeed: number = 2.0; // 转向速度
-
- onLoad() {
- console.log('[EnemyProjectileInstance] 抛掷物实例组件已加载');
- this.setupComponents();
- this.setupProjectileSprite();
- }
-
- start() {
- this.setupCollisionListener();
- }
-
- update(deltaTime: number) {
- // 如果已被标记为销毁,停止更新
- if (this.isDestroyed) {
- return;
- }
-
- // 更新生命周期
- this.currentLifetime += deltaTime;
- if (this.currentLifetime >= this.maxLifetime) {
- console.log('[EnemyProjectileInstance] 抛掷物超时,自动销毁');
- this.isDestroyed = true;
- this.destroyProjectile();
- return;
- }
-
- // 根据弹道类型移动抛掷物
- if (this.useArcTrajectory) {
- this.updateArcTrajectory(deltaTime);
- } else {
- this.moveProjectile(deltaTime);
- }
- }
-
- /**
- * 初始化抛掷物
- * @param config 抛掷物配置
- */
- public init(config: {
- damage: number;
- speed: number;
- direction: Vec3;
- projectileType: string;
- startPosition: Vec3;
- }) {
- console.log('[EnemyProjectileInstance] 初始化抛掷物配置:', config);
-
- this.damage = config.damage;
- this.speed = config.speed;
- this.direction = config.direction.clone().normalize();
- this.projectileType = config.projectileType;
-
- // 注意:不在这里设置位置,因为节点还没有添加到Canvas
- // 位置设置由EnemyProjectile.ts在添加到Canvas后处理
-
- // 初始化弧线弹道
- this.initArcTrajectory();
-
- // 根据类型刷新外观(异步加载)
- this.setupProjectileSprite();
-
- console.log(`[EnemyProjectileInstance] 初始化抛掷物完成: 类型=${this.projectileType}, 伤害=${this.damage}, 速度=${this.speed}`);
- console.log('[EnemyProjectileInstance] 起始位置:', config.startPosition);
- }
-
- /**
- * 获取预制体中已配置的组件
- */
- private setupComponents() {
- // 获取预制体中已配置的刚体组件
- this.rigidBody = this.getComponent(RigidBody2D);
- if (!this.rigidBody) {
- console.error('[EnemyProjectileInstance] 预制体中未找到RigidBody2D组件');
- } else {
- console.log('[EnemyProjectileInstance] 找到RigidBody2D组件');
- }
-
- // 获取预制体中已配置的碰撞器组件
- this.collider = this.getComponent(Collider2D);
- if (!this.collider) {
- console.error('[EnemyProjectileInstance] 预制体中未找到Collider2D组件');
- } else {
- console.log('[EnemyProjectileInstance] 找到Collider2D组件');
- }
-
- // 获取预制体中已配置的精灵组件
- this.sprite = this.getComponent(Sprite);
- if (!this.sprite) {
- console.error('[EnemyProjectileInstance] 预制体中未找到Sprite组件');
- } else {
- console.log('[EnemyProjectileInstance] 找到Sprite组件');
- }
- }
-
- /**
- * 设置抛掷物外观:
- * - 法师僵尸 (magic_bolt) 使用 Animation/EnemyAni/003/ball.png
- * - 其它类型保持预制体默认图片
- */
- private async setupProjectileSprite() {
- if (!this.sprite) return;
- try {
- if (this.projectileType === 'magic_bolt') {
- const loader = BundleLoader.getInstance();
- const framePath = 'EnemyAni/003/ball/spriteFrame';
- const spriteFrame = await loader.loadAssetFromBundle<SpriteFrame>('Animation', framePath, SpriteFrame);
- if (spriteFrame && this.sprite && this.sprite.isValid) {
- this.sprite.spriteFrame = spriteFrame;
- console.log('[EnemyProjectileInstance] 已应用法师抛掷物外观: ball.png');
- }
- } else {
- // 保持预制体默认图片(例如弓箭手箭矢),不做修改
- console.log(`[EnemyProjectileInstance] 使用默认抛掷物外观,类型=${this.projectileType}`);
- }
- } catch (err) {
- console.error('[EnemyProjectileInstance] 加载抛掷物SpriteFrame失败', err);
- }
- }
-
- /**
- * 设置碰撞监听
- */
- private setupCollisionListener() {
- if (this.collider) {
- this.collider.on(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this);
- console.log('[EnemyProjectileInstance] 碰撞监听设置完成(BEGIN_CONTACT绑定)');
- }
- }
-
- /**
- * 移动抛掷物
- */
- private moveProjectile(deltaTime: number) {
- if (!this.rigidBody) return;
-
- // 计算移动向量
- const moveVector = this.direction.clone().multiplyScalar(this.speed * deltaTime);
-
- // 获取当前位置并添加移动向量
- const currentPos = this.node.getWorldPosition();
- const newPos = currentPos.add(moveVector);
-
- // 设置新位置
- this.node.setWorldPosition(newPos);
- }
-
- /**
- * 初始化弧线弹道
- */
- private initArcTrajectory() {
- if (!this.useArcTrajectory) return;
-
- // 寻找最近的墙体作为目标
- this.findNearestWall();
-
- // 计算带45°随机偏移的初始方向
- const baseDir = this.direction.clone().normalize();
- const sign = Math.random() < 0.5 ? 1 : -1;
- const rad = 45 * Math.PI / 180 * sign;
- const cos = Math.cos(rad);
- const sin = Math.sin(rad);
- const offsetDir = new Vec3(
- baseDir.x * cos - baseDir.y * sin,
- baseDir.x * sin + baseDir.y * cos,
- 0
- ).normalize();
-
- this.arcDir = offsetDir;
-
- // 如果找到了目标墙体,设置目标方向
- if (this.targetWallPosition) {
- const currentPos = this.node.getWorldPosition();
- const dirToWall = this.targetWallPosition.clone().subtract(currentPos).normalize();
- this.arcTargetDir = dirToWall;
- } else {
- // 如果没有找到墙体,使用基础方向
- this.arcTargetDir = baseDir;
- }
-
- console.log(`[EnemyProjectileInstance] 初始化弧线弹道: 目标墙体位置=${this.targetWallPosition}`);
- }
-
- /**
- * 寻找最近的墙体
- */
- private findNearestWall() {
- const currentPos = this.node.getWorldPosition();
- let nearestWall: Node = null;
- let nearestDistance = Infinity;
-
- // 方法1:通过EnemyController获取墙体节点
- const enemyController = find('Canvas/GameLevelUI/EnemyController')?.getComponent('EnemyController') as any;
- if (enemyController) {
- const wallNodes = [];
- if (enemyController.topFenceNode) wallNodes.push(enemyController.topFenceNode);
- if (enemyController.bottomFenceNode) wallNodes.push(enemyController.bottomFenceNode);
-
- for (const wall of wallNodes) {
- if (wall && wall.active) {
- const distance = Vec3.distance(currentPos, wall.getWorldPosition());
- if (distance < nearestDistance) {
- nearestDistance = distance;
- nearestWall = wall;
- }
- }
- }
- }
-
- // 方法2:在GameArea中搜索墙体节点
- if (!nearestWall) {
- const gameArea = find('Canvas/GameLevelUI/GameArea');
- if (gameArea) {
- for (let i = 0; i < gameArea.children.length; i++) {
- const child = gameArea.children[i];
- if (this.isWallNode(child) && child.active) {
- const distance = Vec3.distance(currentPos, child.getWorldPosition());
- if (distance < nearestDistance) {
- nearestDistance = distance;
- nearestWall = child;
- }
- }
- }
- }
- }
-
- // 方法3:直接搜索常见墙体路径
- if (!nearestWall) {
- const wallPaths = [
- 'Canvas/GameLevelUI/GameArea/TopFence',
- 'Canvas/GameLevelUI/GameArea/BottomFence',
- 'Canvas/GameLevelUI/Wall',
- 'Canvas/Wall',
- 'Canvas/TopWall',
- 'Canvas/BottomWall'
- ];
-
- for (const path of wallPaths) {
- const wall = find(path);
- if (wall && wall.active) {
- const distance = Vec3.distance(currentPos, wall.getWorldPosition());
- if (distance < nearestDistance) {
- nearestDistance = distance;
- nearestWall = wall;
- }
- }
- }
- }
-
- // 设置目标墙体位置
- if (nearestWall) {
- this.targetWallPosition = nearestWall.getWorldPosition().clone();
- }
- }
- private updateArcTrajectory(deltaTime: number) {
- if (!this.arcDir || !this.arcTargetDir) {
- this.moveProjectile(deltaTime);
- return;
- }
- // 平滑转向到目标方向
- const currentDir = this.arcDir.clone();
- const targetDir = this.arcTargetDir.clone();
- // 插值计算新方向
- const lerpFactor = Math.min(1, this.rotateSpeed * deltaTime);
- const newDir = new Vec3(
- currentDir.x + (targetDir.x - currentDir.x) * lerpFactor,
- currentDir.y + (targetDir.y - currentDir.y) * lerpFactor,
- 0
- ).normalize();
- this.arcDir = newDir;
- this.direction = newDir;
- // 以当前方向移动
- this.moveProjectile(deltaTime);
- }
- private isDestroyed: boolean = false;
- private onBeginContact(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null) {
- if (this.isDestroyed) return;
- const otherNode = otherCollider.node;
- const otherPath = this.getNodePath(otherNode);
- console.log(`[EnemyProjectileInstance] BEGIN_CONTACT -> 节点: ${otherNode.name} (${otherPath}), 有接触信息: ${!!contact}`);
- if (this.isWallNode(otherNode)) {
- // 撞到墙体
- this.hitWall(contact);
- } else if (this.isPlayerBlock(otherNode)) {
- // 撞到玩家方块
- // TODO: 造成伤害(如果有需要)
- this.playHitSound();
- this.destroyProjectile();
- }
- }
- private getNodePath(node: Node): string {
- const names: string[] = [];
- let current: Node | null = node;
- while (current) {
- names.unshift(current.name);
- current = current.parent;
- }
- return names.join('/');
- }
- private isWallNode(node: Node): boolean {
- const name = node.name.toLowerCase();
- return name.includes('fence') || name.includes('wall');
- }
- private isPlayerBlock(node: Node): boolean {
- const path = this.getNodePath(node);
- return path.includes('WeaponBlock') || path.includes('Block');
- }
- private hitWall(contact?: IPhysics2DContact | null) {
- // 计算命中位置(优先采用物理接触点)
- const impactPos = this.getImpactWorldPosition(contact);
- this.spawnWallImpactEffect(impactPos);
- this.playHitSound();
- this.destroyProjectile();
- EventBus.getInstance().emit(GameEvents.ENEMY_DAMAGE_WALL, { damage: this.damage, source: this.node });
- }
- private getImpactWorldPosition(contact?: IPhysics2DContact | null): Vec3 {
- try {
- const c: any = contact;
- if (c && typeof c.getWorldManifold === 'function') {
- const manifold = c.getWorldManifold();
- if (manifold && manifold.points && manifold.points.length > 0) {
- const p = manifold.points[0];
- const pos = new Vec3(p.x, p.y, 0);
- console.log(`[EnemyProjectileInstance] 使用接触点作为命中位置: (${pos.x.toFixed(1)}, ${pos.y.toFixed(1)})`);
- return pos;
- }
- }
- } catch (e) {
- // 忽略异常,使用抛掷物当前位置
- console.warn('[EnemyProjectileInstance] 接触点解析异常,回退为抛掷物当前位置');
- }
- const backup = this.node.getWorldPosition().clone();
- console.log(`[EnemyProjectileInstance] 使用抛掷物当前位置作为命中位置: (${backup.x.toFixed(1)}, ${backup.y.toFixed(1)})`);
- return backup;
- }
- private spawnWallImpactEffect(worldPos: Vec3) {
- const mgr = EnemyAttackEffectManager.instance;
- const type = this.getImpactEffectType();
- if (!mgr || !type) return;
- console.log(`[EnemyProjectileInstance] 触发墙体命中特效: type=${type}, 位置=(${worldPos.x.toFixed(1)}, ${worldPos.y.toFixed(1)})`);
- mgr.playImpactEffect(type, worldPos);
- }
- private getImpactEffectType(): AttackEffectType | null {
- const t = (this.projectileType || '').toLowerCase();
- console.log(`[EnemyProjectileInstance] 解析特效类型,projectileType=${this.projectileType}`);
- if (t === 'magic_bolt' || t.includes('magic')) {
- return AttackEffectType.Mage;
- }
- if (t === 'arrow' || t.includes('arrow')) {
- return AttackEffectType.Archer;
- }
- return null;
- }
- private playHitSound() {
- console.log('[EnemyProjectileInstance] 播放命中音效: data/弹球音效/MagicianAttack.mp3');
- Audio.playEnemySound('data/弹球音效/MagicianAttack.mp3');
- }
- private destroyProjectile() {
- this.isDestroyed = true;
- if (this.node && this.node.isValid) {
- console.log('[EnemyProjectileInstance] 抛掷物节点销毁');
- this.node.destroy();
- }
- }
- onDestroy() {
- // 清理逻辑,如有需要
- }
- }
|