Просмотр исходного кода

寻路管理器 AI行为 决策判断 行为判断

woso_javan 2 месяцев назад
Родитель
Сommit
2937fee716

+ 58 - 18
assets/module_storm_sunder/Prefabs/Tornado.prefab

@@ -25,13 +25,13 @@
         "__id__": 11
       },
       {
-        "__id__": 15
+        "__id__": 17
       }
     ],
     "_active": true,
     "_components": [],
     "_prefab": {
-      "__id__": 25
+      "__id__": 27
     },
     "_lpos": {
       "__type__": "cc.Vec3",
@@ -268,10 +268,13 @@
     "_components": [
       {
         "__id__": 12
+      },
+      {
+        "__id__": 14
       }
     ],
     "_prefab": {
-      "__id__": 14
+      "__id__": 16
     },
     "_lpos": {
       "__type__": "cc.Vec3",
@@ -303,7 +306,7 @@
     "_id": ""
   },
   {
-    "__type__": "cc.CylinderCollider",
+    "__type__": "cc.RigidBody",
     "_name": "",
     "_objFlags": 0,
     "__editorExtras__": {},
@@ -314,8 +317,45 @@
     "__prefab": {
       "__id__": 13
     },
+    "_group": 8,
+    "_type": 4,
+    "_mass": 1,
+    "_allowSleep": true,
+    "_linearDamping": 0.1,
+    "_angularDamping": 0.1,
+    "_useGravity": true,
+    "_linearFactor": {
+      "__type__": "cc.Vec3",
+      "x": 1,
+      "y": 1,
+      "z": 1
+    },
+    "_angularFactor": {
+      "__type__": "cc.Vec3",
+      "x": 1,
+      "y": 1,
+      "z": 1
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "947Kp6R9lECZOZud7zTptC"
+  },
+  {
+    "__type__": "cc.CylinderCollider",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 11
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 15
+    },
     "_material": null,
-    "_isTrigger": false,
+    "_isTrigger": true,
     "_center": {
       "__type__": "cc.Vec3",
       "x": 0,
@@ -323,7 +363,7 @@
       "z": 0
     },
     "_radius": 15,
-    "_height": 7.8,
+    "_height": 8,
     "_direction": 1,
     "_id": ""
   },
@@ -353,9 +393,6 @@
       "__id__": 1
     },
     "_children": [
-      {
-        "__id__": 16
-      },
       {
         "__id__": 18
       },
@@ -364,12 +401,15 @@
       },
       {
         "__id__": 22
+      },
+      {
+        "__id__": 24
       }
     ],
     "_active": true,
     "_components": [],
     "_prefab": {
-      "__id__": 24
+      "__id__": 26
     },
     "_lpos": {
       "__type__": "cc.Vec3",
@@ -406,13 +446,13 @@
     "_objFlags": 0,
     "__editorExtras__": {},
     "_parent": {
-      "__id__": 15
+      "__id__": 17
     },
     "_children": [],
     "_active": true,
     "_components": [],
     "_prefab": {
-      "__id__": 17
+      "__id__": 19
     },
     "_lpos": {
       "__type__": "cc.Vec3",
@@ -462,13 +502,13 @@
     "_objFlags": 0,
     "__editorExtras__": {},
     "_parent": {
-      "__id__": 15
+      "__id__": 17
     },
     "_children": [],
     "_active": true,
     "_components": [],
     "_prefab": {
-      "__id__": 19
+      "__id__": 21
     },
     "_lpos": {
       "__type__": "cc.Vec3",
@@ -518,13 +558,13 @@
     "_objFlags": 0,
     "__editorExtras__": {},
     "_parent": {
-      "__id__": 15
+      "__id__": 17
     },
     "_children": [],
     "_active": true,
     "_components": [],
     "_prefab": {
-      "__id__": 21
+      "__id__": 23
     },
     "_lpos": {
       "__type__": "cc.Vec3",
@@ -574,13 +614,13 @@
     "_objFlags": 0,
     "__editorExtras__": {},
     "_parent": {
-      "__id__": 15
+      "__id__": 17
     },
     "_children": [],
     "_active": true,
     "_components": [],
     "_prefab": {
-      "__id__": 23
+      "__id__": 25
     },
     "_lpos": {
       "__type__": "cc.Vec3",

+ 1 - 2
assets/module_storm_sunder/Script/Component/PropComponent.ts

@@ -39,7 +39,7 @@ export class PropComponent extends Component {
         if (this.status == PropStatus.DIE) return;
 
         this.currentHp -= damage;
-        console.log(`当前道具血量:${this.currentHp}`);
+        // console.log(`当前道具血量:${this.currentHp}`);
 
         if (this.currentHp > 0) {
             const hpPercent = this.currentHp / this.hp;
@@ -68,7 +68,6 @@ export class PropComponent extends Component {
     }
 
     protected onTriggerEnter(event: ITriggerEvent): void {
-        console.log(`道具发生碰撞 onTriggerEnter1111111111111111`);
     }
 
     onTriggerExit(event: ITriggerEvent): void {

+ 154 - 22
assets/module_storm_sunder/Script/Component/TornadoAIComponent.ts

@@ -1,41 +1,173 @@
-import { BoxCollider, Button, Collider, Component, ConeCollider, CylinderCollider, ITriggerEvent, Label, Node, NodeEventType, RigidBody, SphereCollider, Vec3, _decorator, find, game } from 'cc';
-import { StormSunderAudioMgr } from '../Manager/StormSunderAudioMgr';
-import { EventDispatcher } from 'db://assets/core_tgx/easy_ui_framework/EventDispatcher';
-import { GameEvent } from '../Enum/GameEvent';
-import { UIJoyStick } from '../UIJoyStick';
-import { GameMgr, GameStatus } from '../Manager/GameMgr';
-import { PropComponent, PropStatus } from './PropComponent';
-import { PlayerInfo } from './PlayerInfoComponent';
-import { Effect2DUIMgr } from '../Manager/Effect2DUIMgr';
+import { BoxCollider, Component, ITriggerEvent, Node, Vec3, _decorator, randomRange } from 'cc';
+import { PathfindingManager } from '../Manager/PathfindingManager';
 import { PlayerStatus, TornadoComponent } from './TornadoComponent';
+import { PropStatus } from './PropComponent';
+import { GameUtil } from '../GameUtil';
+
 const { ccclass, property } = _decorator;
 
+enum BehaviorType {
+    Move,
+    Collect  //收集最近的道具
+}
 
-/** 龙卷风AI组件*/
+/** 龙卷风AI组件 */
 @ccclass('TornadoAIComponent')
 export class TornadoAIComponent extends TornadoComponent {
 
-    attack_ai: number = 1;
-    speed_ai: number = 1;
-    expPower_ai: number = 1;
+    moveDuration: number = 3; //移动行为持续时间(秒)
+    escapeDuration: number = 5; //逃离行为持续时间(秒)
+    chaseDuration: number = 30; //追击行为持续时间(秒)
+    chaseAIProbability: number = 1; //是否追击 AI 的概率 1是100%
+    chasePlayerProbability: number = 1; //是否追击玩家的概率
+
+    isChasing: boolean = false; // 是否追击中
+    isEscaping: boolean = false; // 是否逃跑中
+    targetNode: Node | null = null; // 目标
+
+    behaviorType: BehaviorType = BehaviorType.Move; // 行为类型
 
-    protected start(): void {
+    protected async start() {
         super.start();
         this.ai = true;
-
         this.playerInfo.nickName = '阿西吧 ai';
-        this.playerInfo.level = 1;
+        this.playerInfo.level = 2;
+        this.currentLv = this.playerInfo.level;
 
-        console.log('playerInfo:', this.playerInfo);
         this.onPlayerInfoHandler();
+        this.decideAction(); // 进入行为循环
+
+        this.radiusTigger.on('onTriggerEnter', this.onRadiusTriggerEnter, this);
+    }
+
+    /** 选择 AI 行为 */
+    private decideAction() {
+        if (this.playerStatus == PlayerStatus.DIE) return; // AI 死亡时不执行行为
+
+        // DOTO 目前固定 100% 选择移动行为(未来可修改概率)
+        const actionType = this.behaviorType;
+
+        if (actionType === BehaviorType.Move) {
+            this.randomMove();
+        } else {
+            const closestItem = this.findClosestItem();
+            if (closestItem) {
+                this.moveToTarget(closestItem, this.moveDuration);
+            } else {
+                this.randomMove();
+            }
+        }
+    }
+
+    /** 随机移动 */
+    private randomMove() {
+        if (this.isChasing || this.isEscaping) return; // 如果正在追击或逃跑,则不进行随机移动
+
+        const randomDirection = new Vec3(randomRange(-50, 50), 0, randomRange(-50, 50));
+        const targetPosition = this.node.position.clone().add(randomDirection);
+        PathfindingManager.getInstance().moveTo(this.node, targetPosition, this.moveDuration);
+        console.log(`AI 触发随机移动行为!`);
+
+        this.scheduleOnce(() => {
+            if (!this.isChasing && !this.isEscaping) { // 再次检查状态,避免重复调用
+                this.decideAction();
+            }
+        }, this.moveDuration);
     }
 
-    protected onTriggerEnter(event: ITriggerEvent): void {
-        super.onTriggerEnter(event)
+    /** 移动到目标 */
+    private moveToTarget(target: Node, duration: number) {
+        PathfindingManager.getInstance().moveTo(this.node, target.position, duration);
+
+        this.scheduleOnce(() => {
+            this.decideAction();
+        }, duration);
+    }
+
+    /** 触发器检测(过程中遇到其他龙卷风) */
+    protected onRadiusTriggerEnter(event: ITriggerEvent): void {
+        const otherCollider = event.otherCollider;
+        const otherNode = otherCollider.node;
+
+        if (event.otherCollider.getGroup() == 1 << 3) {
+            const targetTornado = otherNode.parent.getComponent(TornadoComponent);
+            if (!targetTornado) return;
+            console.log(`AI 触发器检测到其他龙卷风!`)
+            const isTargetAI = targetTornado instanceof TornadoAIComponent;
+            const targetLv = targetTornado.currentLv;
+
+            if (targetLv > this.currentLv) {
+                // 目标等级比自己高 → 逃跑
+                console.log(`AI 逃离行为!`)
+                this.escapeFrom(targetTornado.node);
+            } else if (targetLv < this.currentLv) {
+                // 目标等级比自己低 → 先判断是否追击
+                if (Math.random() < this.chaseAIProbability) {
+                    if (isTargetAI) {
+                        // 目标是 AI,直接追击
+                        this.chaseTarget(targetTornado.node);
+                    } else {
+                        // 目标是玩家,进一步判断是否追击
+                        if (Math.random() < this.chasePlayerProbability) {
+                            this.chaseTarget(targetTornado.node);
+                        } else {
+                            // 不追击,则保持当前行为到持续时间结束
+                            this.continueCurrentBehavior();
+                        }
+                    }
+                } else {
+                    // 不追击,持续当前行为直到时间结束
+                    this.continueCurrentBehavior();
+                }
+            }
+            // 如果等级相同,继续当前行为
+        }
+    }
+
+    /** 继续当前行为直到时间结束 */
+    private continueCurrentBehavior() {
+        this.scheduleOnce(() => {
+            this.decideAction();
+        }, this.moveDuration);
+    }
+
+    /** 追击目标 */
+    private chaseTarget(target: Node) {
+        if (this.isChasing) return;
+        console.log(`AI 追击目标->>>>>>>>>>>>>`);
+
+        this.isChasing = true;
+        this.targetNode = target;
+
+        PathfindingManager.getInstance().followTarget(this, target, 20);
+
+        // 追击时间结束后恢复行为
+        this.scheduleOnce(() => {
+            this.isChasing = false;
+            this.targetNode = null;
+            this.decideAction();
+        }, this.chaseDuration);
+    }
+
+
+    /** 逃离目标 */
+    private escapeFrom(target: Node) {
+        this.isEscaping = true;
+        console.log(`AI 逃离行为->>>>>>>>>>>>>>`);
+        const direction = this.node.position.clone().subtract(target.position).normalize().multiplyScalar(20);
+        const escapePosition = this.node.position.clone().add(direction);
+
+        PathfindingManager.getInstance().moveTo(this.node, escapePosition, this.escapeDuration);
+
+        this.scheduleOnce(() => {
+            this.isEscaping = false;
+            this.decideAction();
+        }, this.escapeDuration);
     }
 
-    protected onTriggerStay(event: ITriggerEvent): void {
-        super.onTriggerStay(event);
-        console.log('阿西吧 干它!!!!!!!!!!!!!!!!!!');
+    /** 查找最近的道具 */
+    private findClosestItem(): Node | null {
+        // TODO: 实现查找最近的道具
+        return null;
     }
 }

+ 20 - 10
assets/module_storm_sunder/Script/Component/TornadoComponent.ts

@@ -34,6 +34,8 @@ export class TornadoComponent extends Component {
     playerStatus: PlayerStatus = PlayerStatus.LIFE;
     playerInfo: PlayerInfo = null;
     isColliding: boolean = false;
+    //当前攻击的道具
+    currentAttackProp: Node = null;
     private _ai: boolean = false;
     public get ai(): boolean {
         return this._ai;
@@ -58,6 +60,7 @@ export class TornadoComponent extends Component {
         this.tornado = this.node.getChildByName('tornado')!;
         this.rigidBody = this.tornado.getComponent(RigidBody)!;
         this.tigger = this.tornado.getComponent(Collider)!;
+        this.radiusTigger = this.node.getChildByName('radiusTigger').getComponent(Collider)!;
 
         const points = this.node.getChildByName('points')!;
         this.points = points.children.map((child) => child);
@@ -69,7 +72,7 @@ export class TornadoComponent extends Component {
 
     protected initPlayer() {
         //DOTO 计算方式根据配置表
-        this.currentLv = 1;
+        this.currentLv = 3;
         this.currentExp = 0;
         this.nextExp = 100;
         this.attack = 20;
@@ -78,7 +81,7 @@ export class TornadoComponent extends Component {
 
         this.playerInfo = {
             nickName: this.nickName,
-            level: 1,
+            level: this.currentLv,
             exp: 0,
         }
     }
@@ -127,9 +130,10 @@ export class TornadoComponent extends Component {
     protected onTriggerStay(event: ITriggerEvent): void {
         if (event.otherCollider.getGroup() == 1 << 4) {
             const otherCollider = event.otherCollider;
-            const propComp = otherCollider.node.getComponent(PropComponent);
+            this.currentAttackProp = otherCollider.node;
 
-            if (!propComp) return;
+            if (!this.currentAttackProp.getComponent(PropComponent)) return;
+            const propComponent = this.currentAttackProp.getComponent(PropComponent);
 
             const currentTime = game.totalTime;
             const nodeId = otherCollider.node.uuid;
@@ -139,19 +143,19 @@ export class TornadoComponent extends Component {
             if (currentTime - lastTime >= this._attackInterval * 1000) {
                 this._lastAttackTime.set(nodeId, currentTime);
 
-                if (propComp.status == PropStatus.DIE) return
+                if (propComponent.status == PropStatus.DIE) return
                 // 造成伤害
-                propComp.hurt(this.attack);
+                propComponent.hurt(this.attack);
 
                 // 检查道具是否被摧毁
-                if (propComp.currentHp <= 0) {
+                if (propComponent.currentHp <= 0) {
                     // 随机选择一个吸收点
                     const randomPoint = this.points[Math.floor(Math.random() * this.points.length)];
                     if (randomPoint) {
                         otherCollider.node.parent = randomPoint;
                         otherCollider.node.setPosition(Vec3.ZERO);
-                        propComp.swallow();
-                        this.addExpByProp(propComp);
+                        propComponent.swallow();
+                        this.addExpByProp();
                     }
                 }
             }
@@ -159,6 +163,9 @@ export class TornadoComponent extends Component {
     }
 
     onTriggerExit(event: ITriggerEvent) {
+        if (event.otherCollider.getGroup() == 1 << 4) {
+            this.currentAttackProp = null;
+        }
     }
 
     protected update(deltaTime: number) {
@@ -187,7 +194,10 @@ export class TornadoComponent extends Component {
         this.node.setPosition(this.node.position.x + playerX, 0, this.node.position.z - playerZ);
     }
 
-    private addExpByProp(propComp: PropComponent) {
+    protected addExpByProp() {
+        if (!this.currentAttackProp) return;
+
+        const propComp = this.currentAttackProp.getComponent(PropComponent);
         const propExp = propComp.exp;
         this.currentExp += propExp;
         if (this.currentExp >= this.nextExp) {

+ 44 - 0
assets/module_storm_sunder/Script/Manager/PathfindingManager.ts

@@ -0,0 +1,44 @@
+import { Vec3, Node, tween, Component } from 'cc';
+
+export class PathfindingManager {
+    private static instance: PathfindingManager;
+
+    static getInstance(): PathfindingManager {
+        if (!this.instance) {
+            this.instance = new PathfindingManager();
+        }
+        return this.instance;
+    }
+
+    /**
+     * 让节点移动到目标点
+     * @param node 需要移动的对象
+     * @param targetPosition 目标点
+     * @param duration 移动持续时间(秒)
+     */
+    public moveTo(node: Node, targetPosition: Vec3, duration: number) {
+        tween(node)
+            .to(duration, { position: targetPosition })
+            .start();
+    }
+
+    /** 让 AI 持续追踪目标 */
+    followTarget(aiComponent: Component, targetNode: Node, moveSpeed: number) {
+        if (!aiComponent || !targetNode) return;
+
+        aiComponent.schedule(() => {
+            if (!targetNode.isValid) return; // 目标被销毁就停止追击
+
+            const aiNode = aiComponent.node;
+            const targetPos = targetNode.worldPosition;
+            const myPos = aiNode.worldPosition;
+
+            const direction = targetPos.clone().subtract(myPos).normalize();
+            const moveStep = direction.multiplyScalar(moveSpeed * 0.016); // 16ms 约等于 60FPS
+
+            aiNode.setWorldPosition(myPos.add(moveStep));
+        }, 0.016); // 60FPS 更新追击
+    }
+
+}
+

+ 9 - 0
assets/module_storm_sunder/Script/Manager/PathfindingManager.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.23",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "7d3f465f-d0e8-49a7-b26c-555c65c39046",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 27 - 0
assets/module_storm_sunder/Script/Manager/PropMgr.ts

@@ -123,6 +123,33 @@ export class PropMgr {
         return false; // 没有检测到障碍物
     }
 
+    /**
+     * 获取距离玩家节点最近的道具节点
+     * @param playerNode 玩家节点
+     * @returns 距离最近的道具节点(如果存在),否则返回 null
+     */
+    getNearestProp(playerNode: Node): Node | null {
+        if (!playerNode) return null;
+
+        let minDistance = Infinity;
+        let nearestProp: Node | null = null;
+
+        const playerPos = playerNode.worldPosition;
+        const propsNodes = StormSunderGlobalInstance.instance.props;
+
+        propsNodes.children.forEach(prop => {
+            const propPos = prop.worldPosition;
+            const distance = Vec3.distance(playerPos, propPos);
+
+            if (distance < minDistance) {
+                minDistance = distance;
+                nearestProp = prop;
+            }
+        });
+
+        return nearestProp;
+    }
+
     /** 删除移动道具*/
     removeMovingProp() {
         this.curMovePropsCount--;

+ 1 - 1
assets/start/Start.ts

@@ -68,7 +68,7 @@ export class Start extends Component {
             EPhysics2DDrawFlags.Joint |
             EPhysics2DDrawFlags.Shape;
 
-        PhysicsSystem2D.instance.debugDrawFlags = 0; // 启用调试绘制
+        PhysicsSystem2D.instance.debugDrawFlags = 1; // 启用调试绘制
 
         tgxModuleContext.setDefaultModule(ModuleDef.BASIC);
 

+ 1 - 1
settings/v2/packages/project.json

@@ -39,7 +39,7 @@
       "0": 1,
       "1": 30,
       "2": 30,
-      "3": 22,
+      "3": 30,
       "4": 14,
       "5": 32,
       "6": 64,