|
|
@@ -0,0 +1,479 @@
|
|
|
+import { _decorator, Component, Node, Vec3, RigidBody2D, Collider2D, Contact2DType, IPhysics2DContact, Sprite, find } from 'cc';
|
|
|
+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();
|
|
|
+
|
|
|
+ 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组件');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 设置抛掷物外观,使用3.png
|
|
|
+ */
|
|
|
+ private async setupProjectileSprite() {
|
|
|
+ if (!this.sprite) return;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 设置碰撞监听
|
|
|
+ */
|
|
|
+ private setupCollisionListener() {
|
|
|
+ if (this.collider) {
|
|
|
+ this.collider.on(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this);
|
|
|
+ console.log('[EnemyProjectileInstance] 碰撞监听设置完成');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 移动抛掷物
|
|
|
+ */
|
|
|
+ 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();
|
|
|
+ console.log(`[EnemyProjectileInstance] 找到目标墙体: ${nearestWall.name}, 距离: ${nearestDistance.toFixed(2)}`);
|
|
|
+ } else {
|
|
|
+ console.warn('[EnemyProjectileInstance] 未找到可用的墙体目标');
|
|
|
+ // 设置一个默认的目标位置(屏幕中心下方)
|
|
|
+ this.targetWallPosition = new Vec3(0, -200, 0);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 更新弧线弹道
|
|
|
+ */
|
|
|
+ private updateArcTrajectory(deltaTime: number) {
|
|
|
+ if (!this.arcDir || !this.arcTargetDir) return;
|
|
|
+
|
|
|
+ // 重新寻找墙体目标(动态追踪)
|
|
|
+ if (!this.targetWallPosition) {
|
|
|
+ this.findNearestWall();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新目标方向
|
|
|
+ if (this.targetWallPosition) {
|
|
|
+ const currentPos = this.node.getWorldPosition();
|
|
|
+ const dirToWall = this.targetWallPosition.clone().subtract(currentPos).normalize();
|
|
|
+ this.arcTargetDir.set(dirToWall);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算转向
|
|
|
+ const rotateFactor = this.rotateSpeed * deltaTime;
|
|
|
+ this.arcDir = this.arcDir.lerp(this.arcTargetDir, rotateFactor).normalize();
|
|
|
+
|
|
|
+ // 计算移动向量
|
|
|
+ const moveVector = this.arcDir.clone().multiplyScalar(this.speed * deltaTime);
|
|
|
+
|
|
|
+ // 获取当前位置并添加移动向量
|
|
|
+ const currentPos = this.node.getWorldPosition();
|
|
|
+ const newPos = currentPos.add(moveVector);
|
|
|
+
|
|
|
+ // 设置新位置
|
|
|
+ this.node.setWorldPosition(newPos);
|
|
|
+
|
|
|
+ // 更新节点朝向(可选)
|
|
|
+ const angle = Math.atan2(this.arcDir.y, this.arcDir.x) * 180 / Math.PI;
|
|
|
+ this.node.angle = angle;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 防止重复销毁的标志
|
|
|
+ private isDestroyed: boolean = false;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 碰撞开始事件
|
|
|
+ */
|
|
|
+ private onBeginContact(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null) {
|
|
|
+ // 如果已经被销毁,不再处理碰撞
|
|
|
+ if (this.isDestroyed) {
|
|
|
+ console.log(`[EnemyProjectileInstance] 抛掷物已被销毁,忽略碰撞事件`);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const otherNode = otherCollider.node;
|
|
|
+ const nodeName = otherNode.name;
|
|
|
+
|
|
|
+ console.log(`[EnemyProjectileInstance] 抛掷物碰撞检测 - 碰撞对象: ${nodeName}, 节点路径: ${this.getNodePath(otherNode)}`);
|
|
|
+
|
|
|
+ // 抛掷物的目标是墙体,检查是否碰到墙体
|
|
|
+ if (this.isWallNode(otherNode)) {
|
|
|
+ console.log(`[EnemyProjectileInstance] 抛掷物击中目标墙体: ${nodeName}, 准备销毁`);
|
|
|
+ this.hitWall();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log(`[EnemyProjectileInstance] 抛掷物碰撞到非目标对象: ${nodeName}, 继续飞行`);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取节点的完整路径
|
|
|
+ */
|
|
|
+ private getNodePath(node: Node): string {
|
|
|
+ const path = [];
|
|
|
+ let current = node;
|
|
|
+ while (current) {
|
|
|
+ path.unshift(current.name);
|
|
|
+ current = current.parent;
|
|
|
+ }
|
|
|
+ return path.join('/');
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 检查是否为墙体节点
|
|
|
+ */
|
|
|
+ private isWallNode(node: Node): boolean {
|
|
|
+ const nodeName = node.name.toLowerCase();
|
|
|
+
|
|
|
+ // 检查节点名称是否包含墙体关键词
|
|
|
+ const wallKeywords = ['wall', 'fence', 'jiguang', '墙', '围栏'];
|
|
|
+ const isWallByName = wallKeywords.some(keyword => nodeName.includes(keyword));
|
|
|
+
|
|
|
+ // 检查节点是否有Wall组件
|
|
|
+ const hasWallComponent = node.getComponent('Wall') !== null;
|
|
|
+
|
|
|
+ return isWallByName || hasWallComponent;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 检查是否为玩家方块
|
|
|
+ */
|
|
|
+ private isPlayerBlock(node: Node): boolean {
|
|
|
+ // 检查节点名称或标签
|
|
|
+ return node.name.includes('Block') || node.name.includes('WeaponBlock');
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 击中墙体
|
|
|
+ */
|
|
|
+ private hitWall() {
|
|
|
+ // 防止重复销毁
|
|
|
+ if (this.isDestroyed) {
|
|
|
+ console.log(`[EnemyProjectileInstance] 抛掷物已被销毁,忽略hitWall调用`);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.isDestroyed = true;
|
|
|
+ console.log(`[EnemyProjectileInstance] 抛掷物击中墙体,造成 ${this.damage} 点伤害,开始销毁流程`);
|
|
|
+
|
|
|
+ // 对墙体造成伤害
|
|
|
+ EventBus.getInstance().emit(GameEvents.ENEMY_DAMAGE_WALL, {
|
|
|
+ damage: this.damage,
|
|
|
+ source: this.node
|
|
|
+ });
|
|
|
+
|
|
|
+ // 播放击中音效
|
|
|
+ this.playHitSound();
|
|
|
+
|
|
|
+ // 销毁抛掷物
|
|
|
+ this.destroyProjectile();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 击中玩家方块(已移除,抛掷物不攻击玩家方块)
|
|
|
+ */
|
|
|
+ /*
|
|
|
+ private hitPlayerBlock(blockNode: Node) {
|
|
|
+ // 防止重复销毁
|
|
|
+ if (this.isDestroyed) {
|
|
|
+ console.log(`[EnemyProjectileInstance] 抛掷物已被销毁,忽略hitPlayerBlock调用`);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.isDestroyed = true;
|
|
|
+ console.log(`[EnemyProjectileInstance] 抛掷物击中玩家方块: ${blockNode.name},开始销毁流程`);
|
|
|
+
|
|
|
+ // 对玩家方块造成伤害 - 使用事件系统
|
|
|
+ EventBus.getInstance().emit(GameEvents.ENEMY_DAMAGE_PLAYER_BLOCK, {
|
|
|
+ damage: this.damage,
|
|
|
+ blockNode: blockNode,
|
|
|
+ source: this.node
|
|
|
+ });
|
|
|
+
|
|
|
+ // 播放击中音效
|
|
|
+ this.playHitSound();
|
|
|
+
|
|
|
+ // 销毁抛掷物
|
|
|
+ this.destroyProjectile();
|
|
|
+ }
|
|
|
+ */
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 播放击中音效
|
|
|
+ */
|
|
|
+ private playHitSound() {
|
|
|
+ // 根据抛掷物类型播放不同音效
|
|
|
+ let soundPath = '';
|
|
|
+ switch (this.projectileType) {
|
|
|
+ case 'arrow':
|
|
|
+ soundPath = 'data/弹球音效/fire';
|
|
|
+ break;
|
|
|
+ case 'magic_bolt':
|
|
|
+ soundPath = 'data/弹球音效/fire';
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ soundPath = 'data/弹球音效/fire';
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (soundPath) {
|
|
|
+ Audio.playWeaponSound(soundPath);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 销毁抛掷物
|
|
|
+ */
|
|
|
+ private destroyProjectile() {
|
|
|
+ // 防止重复销毁
|
|
|
+ if (this.isDestroyed && (!this.node || !this.node.isValid)) {
|
|
|
+ console.log('[EnemyProjectileInstance] 抛掷物已被销毁,跳过重复销毁');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.node && this.node.isValid) {
|
|
|
+ console.log('[EnemyProjectileInstance] 销毁抛掷物节点');
|
|
|
+ this.node.destroy();
|
|
|
+ } else {
|
|
|
+ console.log('[EnemyProjectileInstance] 抛掷物节点已无效或不存在');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ onDestroy() {
|
|
|
+ // 清理碰撞监听
|
|
|
+ if (this.collider) {
|
|
|
+ this.collider.off(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this);
|
|
|
+ }
|
|
|
+ console.log('[EnemyProjectileInstance] 抛掷物实例组件已销毁');
|
|
|
+ }
|
|
|
+}
|