| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635 |
- import { _decorator, Component, Node, Vec2, Vec3, RigidBody2D, Collider2D, Contact2DType, IPhysics2DContact, find, Prefab, instantiate, UITransform, resources, sp } from 'cc';
- import { WeaponBlockExample } from './WeaponBlockExample';
- const { ccclass, property } = _decorator;
- /**
- * WeaponBullet - 统一的子弹控制器
- *
- * 整合了原 BulletController 的所有功能:
- * 1. 自动瞄准最近敌人
- * 2. 物理属性设置
- * 3. 生命周期管理
- * 4. 特效系统
- * 5. 多种子弹行为
- *
- * 支持的子弹行为:
- * - explosive: 击中敌人立即销毁并播放hitEffect
- * - piercing: 击中敌人不销毁,持续飞行
- * - ricochet: 弹射子弹,可设置弹射次数
- * - boomerang: 回旋镖,延迟后返回
- * - area_burn: 区域灼烧效果
- * - homing: 导弹追踪
- */
- export type BulletBehaviour = 'explosive' | 'piercing' | 'ricochet' | 'boomerang' | 'area_burn' | 'homing' | 'normal';
- export interface BulletInitData {
- behavior: BulletBehaviour;
- speed: number;
- damage: number;
- hitEffect?: string; // resources 路径
- trailEffect?: string; // resources 路径
- lifetime?: number; // 子弹生存时间
- ricochetCount?: number; // ricochet 弹射次数
- ricochetAngle?: number; // 每次弹射随机角度 (deg)
- arcHeight?: number; // boomerang
- returnDelay?: number;
- burnDuration?: number; // area burn
- homingDelay?: number; // homing missile
- homingStrength?: number;
- // 从 BulletController 迁移的配置
- firePosition?: Vec3; // 发射位置(世界坐标)
- autoTarget?: boolean; // 是否自动瞄准最近敌人
- }
- @ccclass('WeaponBullet')
- export class WeaponBullet extends Component {
- // === 基础配置 ===
- @property
- public speed: number = 300;
- @property
- public damage: number = 1;
- @property
- public lifetime: number = 5;
- @property({ tooltip: '击中特效路径(resources 相对路径,例如 Animation/tx/tx0001)' })
- public hitEffectPath: string = '';
- @property({ tooltip: '拖尾特效路径' })
- public trailEffectPath: string = '';
- @property({ tooltip: '是否自动瞄准最近敌人' })
- public autoTarget: boolean = true;
- // === 子弹行为配置 ===
- private behavior: BulletBehaviour = 'explosive';
- private ricochetLeft = 0;
- private ricochetAngle = 30; // deg
- // boomerang
- private boomerangTimer = 0;
- private boomerangReturnDelay = 1;
- private initialDir: Vec3 = new Vec3();
- // area burn
- private burnDuration = 5;
- // homing
- private homingDelay = 0.3;
- private homingStrength = 0.8;
- private homingTimer = 0;
- // === 从 BulletController 迁移的属性 ===
- private targetEnemy: Node = null;
- private rigidBody: RigidBody2D = null;
- private lifeTimer: number = 0;
- private direction: Vec3 = new Vec3(1, 0, 0);
- private firePosition: Vec3 = null;
- private isInitialized: boolean = false;
- private needsPhysicsSetup: boolean = false;
- /**
- * 初始化子弹配置
- */
- public init(cfg: BulletInitData) {
- this.behavior = cfg.behavior || 'explosive';
- this.speed = cfg.speed || this.speed;
- this.damage = cfg.damage || this.damage;
- this.hitEffectPath = cfg.hitEffect || this.hitEffectPath;
- this.trailEffectPath = cfg.trailEffect || this.trailEffectPath;
- this.lifetime = cfg.lifetime !== undefined ? cfg.lifetime : this.lifetime;
- this.autoTarget = cfg.autoTarget !== undefined ? cfg.autoTarget : this.autoTarget;
-
- // 子弹行为配置
- if (cfg.ricochetCount !== undefined) this.ricochetLeft = cfg.ricochetCount;
- if (cfg.ricochetAngle) this.ricochetAngle = cfg.ricochetAngle;
- if (cfg.returnDelay) this.boomerangReturnDelay = cfg.returnDelay;
- if (cfg.burnDuration) this.burnDuration = cfg.burnDuration;
- if (cfg.homingDelay) {
- this.homingDelay = cfg.homingDelay;
- this.homingTimer = cfg.homingDelay;
- }
- if (cfg.homingStrength) this.homingStrength = cfg.homingStrength;
-
- // 发射位置设置
- if (cfg.firePosition) {
- this.setFirePosition(cfg.firePosition);
- }
-
- // 兼容性处理
- if (this.behavior === 'normal') this.behavior = 'explosive';
-
- console.log('🔫 WeaponBullet 初始化完成:', {
- behavior: this.behavior,
- speed: this.speed,
- damage: this.damage,
- autoTarget: this.autoTarget
- });
- }
- /**
- * 设置子弹的发射位置(从 BulletController 迁移)
- * @param position 发射位置(世界坐标)
- */
- public setFirePosition(position: Vec3) {
- this.firePosition = position.clone();
- this.needsPhysicsSetup = true;
- console.log('🎯 子弹发射位置已设置:', this.firePosition);
- }
- /**
- * 设置特效路径(从 BulletController 迁移)
- */
- public setEffectPaths(hit: string | null, trail?: string | null) {
- if (hit) this.hitEffectPath = hit;
- if (trail) this.trailEffectPath = trail;
- }
- onLoad() {
- this.rigidBody = this.getComponent(RigidBody2D);
- const collider = this.getComponent(Collider2D);
- if (collider) {
- collider.on(Contact2DType.BEGIN_CONTACT, this.onHit, this);
- }
- }
- start() {
- console.log('🚀 WeaponBullet start()开始...');
-
- // 设置生命周期
- this.lifeTimer = this.lifetime;
-
- // 如果需要设置物理属性,延迟处理
- if (this.needsPhysicsSetup) {
- this.scheduleOnce(() => {
- this.setupPhysics();
- }, 0.05);
- } else {
- // 直接初始化
- this.setupPhysics();
- }
-
- // 添加拖尾特效
- if (this.behavior === 'piercing' && this.trailEffectPath) {
- this.attachTrail();
- }
-
- console.log('✅ WeaponBullet start()完成');
- }
- /**
- * 设置物理属性(从 BulletController 迁移)
- */
- private setupPhysics() {
- console.log('🔧 开始设置子弹物理属性...');
-
- // 确保有刚体组件
- if (!this.rigidBody) {
- console.error('❌ 子弹预制体缺少RigidBody2D组件');
- return;
- }
-
- const collider = this.getComponent(Collider2D);
- if (!collider) {
- console.error('❌ 子弹预制体缺少Collider2D组件');
- return;
- }
-
- // 设置物理属性
- this.rigidBody.enabledContactListener = true;
- this.rigidBody.gravityScale = 0; // 不受重力影响
- this.rigidBody.linearDamping = 0; // 无阻尼
- this.rigidBody.angularDamping = 0; // 无角阻尼
-
- console.log('✅ 物理组件设置成功');
-
- // 设置子弹位置
- if (this.firePosition) {
- this.setPositionInGameArea();
- }
-
- // 初始化方向和速度
- this.initializeDirection();
- this.isInitialized = true;
-
- console.log('✅ 子弹物理属性设置完成');
- }
- /**
- * 在GameArea中设置子弹位置(从 BulletController 迁移)
- */
- private setPositionInGameArea() {
- const gameArea = find('Canvas/GameLevelUI/GameArea');
- if (gameArea) {
- const gameAreaTransform = gameArea.getComponent(UITransform);
- if (gameAreaTransform) {
- const localPos = gameAreaTransform.convertToNodeSpaceAR(this.firePosition);
- this.node.position = localPos;
- console.log('✅ 子弹位置已设置:', localPos);
- }
- }
- }
- /**
- * 初始化子弹方向(从 BulletController 迁移并增强)
- */
- private initializeDirection() {
- console.log('🎯 开始初始化子弹方向...');
-
- // 如果启用自动瞄准,寻找最近的敌人
- if (this.autoTarget) {
- this.findNearestEnemy();
- }
-
- // 设置方向
- if (this.targetEnemy && this.autoTarget) {
- this.setDirectionToTarget();
- } else {
- // 随机方向或使用默认方向
- const randomAngle = Math.random() * Math.PI * 2;
- this.direction = new Vec3(Math.cos(randomAngle), Math.sin(randomAngle), 0);
- console.log('🎲 使用随机方向');
- }
-
- // 保存初始方向(用于boomerang)
- this.initialDir.set(this.direction);
-
- // 应用速度
- this.applyVelocity();
- }
- /**
- * 寻找最近的敌人(从 BulletController 迁移)
- */
- private findNearestEnemy() {
- const enemyContainer = find('Canvas/GameLevelUI/enemyContainer');
- if (!enemyContainer) {
- console.log('❌ 未找到敌人容器');
- return;
- }
-
- const enemies = enemyContainer.children.filter(child =>
- child.active &&
- (child.name.toLowerCase().includes('enemy') ||
- child.getComponent('EnemyInstance') !== null)
- );
-
- if (enemies.length === 0) {
- console.log('❌ 没有找到敌人');
- return;
- }
-
- let nearestEnemy: Node = null;
- let nearestDistance = Infinity;
- const bulletPos = this.node.worldPosition;
-
- for (const enemy of enemies) {
- const distance = Vec3.distance(bulletPos, enemy.worldPosition);
- if (distance < nearestDistance) {
- nearestDistance = distance;
- nearestEnemy = enemy;
- }
- }
-
- if (nearestEnemy) {
- this.targetEnemy = nearestEnemy;
- console.log(`🎯 锁定目标: ${nearestEnemy.name}`);
- }
- }
- /**
- * 设置朝向目标的方向(从 BulletController 迁移)
- */
- private setDirectionToTarget() {
- if (!this.targetEnemy) return;
-
- const targetPos = this.targetEnemy.worldPosition;
- const currentPos = this.node.worldPosition;
-
- this.direction = targetPos.clone().subtract(currentPos).normalize();
- console.log('🎯 方向已设置,朝向目标敌人');
- }
- /**
- * 应用速度(从 BulletController 迁移并增强)
- */
- private applyVelocity() {
- if (!this.rigidBody || !this.direction) {
- console.error('❌ 无法应用速度:缺少刚体或方向');
- return;
- }
-
- // 覆盖速度值(全局控制)
- const weaponGlobal = WeaponBlockExample.getInstance && WeaponBlockExample.getInstance();
- if (weaponGlobal) {
- this.speed = weaponGlobal.getCurrentBulletSpeed();
- }
-
- const velocity = new Vec2(
- this.direction.x * this.speed,
- this.direction.y * this.speed
- );
-
- this.rigidBody.linearVelocity = velocity;
- console.log('✅ 子弹速度已应用:', velocity);
- }
- /**
- * 碰撞处理(整合原有逻辑)
- */
- private onHit(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null) {
- const otherNode = otherCollider.node;
- console.log('💥 子弹碰撞:', otherNode.name);
-
- // 获取碰撞世界坐标
- let contactWorldPos: Vec3 = null;
- if (contact && (contact as any).getWorldManifold) {
- const wm = (contact as any).getWorldManifold();
- if (wm && wm.points && wm.points.length > 0) {
- contactWorldPos = new Vec3(wm.points[0].x, wm.points[0].y, 0);
- }
- }
- if (!contactWorldPos) {
- contactWorldPos = otherNode.worldPosition.clone();
- }
- // 检查是否击中敌人
- if (this.isEnemyNode(otherNode)) {
- console.log('🎯 击中敌人:', otherNode.name);
-
- // 对敌人造成伤害
- this.damageEnemy(otherNode);
-
- // 播放击中特效
- this.spawnHitEffect(contactWorldPos);
- // 根据子弹行为处理
- this.handleBehaviorOnHit(otherNode, contactWorldPos);
- } else if (otherNode.name.toLowerCase().includes('wall')) {
- console.log('🧱 击中墙体,销毁子弹');
- this.node.destroy();
- }
- }
- /**
- * 根据子弹行为处理击中逻辑
- */
- private handleBehaviorOnHit(enemyNode: Node, contactPos: Vec3) {
- switch (this.behavior) {
- case 'explosive':
- this.node.destroy();
- break;
- case 'piercing':
- // 继续飞行,不销毁
- break;
- case 'ricochet':
- if (this.ricochetLeft > 0) {
- this.ricochetLeft--;
- this.bounceDirection();
- } else {
- this.node.destroy();
- }
- break;
- case 'boomerang':
- // 命中后照样返程,由 boomerangTimer 处理,不销毁
- break;
- case 'area_burn':
- // 在地面生成灼烧特效
- this.spawnEffect(this.trailEffectPath, contactPos, true);
- this.scheduleOnce(() => {/* burn area ends */}, this.burnDuration);
- this.node.destroy();
- break;
- case 'homing':
- this.node.destroy();
- break;
- }
- }
- /**
- * 判断是否为敌人节点(从 BulletController 迁移)
- */
- private isEnemyNode(node: Node): boolean {
- const name = node.name.toLowerCase();
- return name.includes('enemy') ||
- name.includes('敌人') ||
- node.getComponent('EnemyInstance') !== null ||
- node.getComponent('EnemyComponent') !== null;
- }
- /**
- * 对敌人造成伤害(从 BulletController 迁移)
- */
- private damageEnemy(enemyNode: Node) {
- console.log('⚔️ 对敌人造成伤害:', this.damage);
-
- // 尝试调用敌人的受伤方法
- const enemyInstance = enemyNode.getComponent('EnemyInstance') as any;
- if (enemyInstance) {
- if (typeof enemyInstance.takeDamage === 'function') {
- enemyInstance.takeDamage(this.damage);
- return;
- }
- if (typeof enemyInstance.health === 'number') {
- enemyInstance.health -= this.damage;
- if (enemyInstance.health <= 0) {
- enemyNode.destroy();
- }
- return;
- }
- }
-
- // 备用方案:通过EnemyController
- const enemyController = find('Canvas/GameLevelUI/EnemyController')?.getComponent('EnemyController') as any;
- if (enemyController && typeof enemyController.damageEnemy === 'function') {
- enemyController.damageEnemy(enemyNode, this.damage);
- }
- }
- update(dt: number) {
- // 只有在初始化完成后才进行更新
- if (!this.isInitialized) return;
-
- // 生命周期管理
- this.updateLifetime(dt);
-
- // 检查是否飞出游戏区域
- this.checkOutOfBounds();
-
- // 子弹行为更新
- this.updateBehavior(dt);
- }
- /**
- * 更新生命周期
- */
- private updateLifetime(dt: number) {
- if (this.behavior === 'piercing' || this.behavior === 'boomerang') {
- this.lifeTimer -= dt;
- if (this.lifeTimer <= 0) {
- this.node.destroy();
- return;
- }
- }
- }
- /**
- * 检查越界(从 BulletController 迁移)
- */
- private checkOutOfBounds() {
- const canvas = find('Canvas');
- if (!canvas || !this.node || !this.node.isValid) return;
- const uiTransform = canvas.getComponent(UITransform);
- if (!uiTransform) return;
- const halfWidth = uiTransform.width / 2;
- const halfHeight = uiTransform.height / 2;
- const center = canvas.worldPosition;
- const left = center.x - halfWidth;
- const right = center.x + halfWidth;
- const bottom = center.y - halfHeight;
- const top = center.y + halfHeight;
- // 允许子弹稍微超出边界后再销毁,避免边缘误差
- const margin = 100;
- const pos = this.node.worldPosition;
- if (pos.x < left - margin || pos.x > right + margin ||
- pos.y < bottom - margin || pos.y > top + margin) {
- this.node.destroy();
- }
- }
- /**
- * 更新子弹行为
- */
- private updateBehavior(dt: number) {
- // boomerang返回
- if (this.behavior === 'boomerang') {
- this.boomerangTimer += dt;
- if (this.boomerangTimer >= this.boomerangReturnDelay) {
- this.direction.multiplyScalar(-1);
- if (this.rigidBody) {
- this.rigidBody.linearVelocity = new Vec2(
- this.direction.x * this.speed,
- this.direction.y * this.speed
- );
- }
- this.behavior = 'piercing'; // 之后按穿透逻辑处理
- }
- }
- // homing 导弹
- if (this.behavior === 'homing') {
- if (this.homingTimer > 0) {
- this.homingTimer -= dt;
- } else {
- const enemyContainer = find('Canvas/GameLevelUI/enemyContainer');
- if (enemyContainer && enemyContainer.children.length > 0) {
- let nearest: Node = null;
- let dist = 1e9;
- for (const e of enemyContainer.children) {
- if (!e.active) continue;
- const d = Vec3.distance(this.node.worldPosition, e.worldPosition);
- if (d < dist) {
- dist = d;
- nearest = e;
- }
- }
- if (nearest) {
- const dir = nearest.worldPosition.clone().subtract(this.node.worldPosition).normalize();
- // 线性插值方向
- this.direction.lerp(dir, this.homingStrength * dt);
- if (this.rigidBody) {
- this.rigidBody.linearVelocity = new Vec2(
- this.direction.x * this.speed,
- this.direction.y * this.speed
- );
- }
- }
- }
- }
- }
- }
- /**
- * 添加拖尾特效
- */
- private attachTrail() {
- this.spawnEffect(this.trailEffectPath, new Vec3(), true, this.node);
- }
- /**
- * 生成特效(整合并增强)
- */
- private spawnEffect(path: string, worldPos: Vec3, loop = false, parent?: Node) {
- if (!path) return;
-
- const match = path.match(/tx\/(tx\d{4})/);
- const code = match ? match[1] : '';
- const skeletonPath = code ? `Animation/WeaponTx/${code}/${code}` : path;
-
- console.log('✨ 尝试加载特效:', skeletonPath);
- resources.load(skeletonPath, sp.SkeletonData, (err, skData: sp.SkeletonData) => {
- if (err) {
- console.warn('加载特效失败:', skeletonPath, err);
- return;
- }
- const effectNode = new Node('Effect');
- const skeleton: sp.Skeleton = effectNode.addComponent(sp.Skeleton);
- skeleton.skeletonData = skData;
- skeleton.setAnimation(0, 'animation', loop);
- const targetParent = parent || find('Canvas/GameLevelUI/GameArea') || find('Canvas');
- if (targetParent) {
- targetParent.addChild(effectNode);
- if (parent) {
- effectNode.setPosition(0, 0, 0);
- } else {
- const parentTrans = targetParent.getComponent(UITransform);
- if (parentTrans) {
- const localPos = parentTrans.convertToNodeSpaceAR(worldPos);
- effectNode.position = localPos;
- } else {
- effectNode.setWorldPosition(worldPos.clone());
- }
- }
- }
- if (!loop) {
- skeleton.setCompleteListener(() => {
- effectNode.destroy();
- });
- }
- });
- }
- /**
- * 生成击中特效(从 BulletController 迁移)
- */
- private spawnHitEffect(worldPos: Vec3) {
- this.spawnEffect(this.hitEffectPath, worldPos, false);
- }
- /**
- * 弹射方向计算
- */
- private bounceDirection() {
- // 简单随机旋转 ricochetAngle
- const sign = Math.random() < 0.5 ? -1 : 1;
- const rad = this.ricochetAngle * (Math.PI / 180) * sign;
- const cos = Math.cos(rad), sin = Math.sin(rad);
- const nx = this.direction.x * cos - this.direction.y * sin;
- const ny = this.direction.x * sin + this.direction.y * cos;
- this.direction.set(nx, ny, 0);
- if (this.rigidBody) {
- this.rigidBody.linearVelocity = new Vec2(
- this.direction.x * this.speed,
- this.direction.y * this.speed
- );
- }
- }
- }
|