第36节:AI集成与3D场景中的智能NPC
第36节:AI集成与3D场景中的智能NPC
概述
人工智能是现代游戏和虚拟世界的核心技术,通过智能NPC(非玩家角色)创造生动、可信的虚拟环境。本节将深入探索路径规划算法、行为树系统、机器学习驱动的NPC行为,以及大规模群体模拟技术。

智能NPC系统架构:
核心原理深度解析
路径规划算法体系
智能NPC导航的核心算法对比:
| 算法类型 | 原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| A* | 启发式搜索 | 最优路径、效率高 | 内存占用大 | 静态环境、精确寻路 |
| Dijkstra | 广度优先搜索 | 保证最短路径 | 计算量大 | 权重图、网络路由 |
| Navigation Mesh | 多边形分割 | 自然移动、3D支持 | 预处理复杂 | 复杂地形、游戏NPC |
| Potential Fields | 势场计算 | 实时避障、平滑 | 局部最优 | 动态避障、群体移动 |
| RVO | 相互速度障碍 | 无碰撞移动 | 计算复杂 | 密集人群、实时避障 |
行为树与状态机对比
有限状态机(FSM):
状态集合:{空闲, 巡逻, 追逐, 攻击, 逃跑}
状态转移:条件驱动
优点:实现简单、直观
缺点:状态爆炸、难以维护
行为树(Behavior Tree):
树状结构:选择器→序列→条件→动作
节点类型:Composite, Decorator, Leaf
优点:模块化、可复用、易调试
缺点:学习曲线较陡
完整代码实现
高级智能NPC系统
<template><div class="ai-npc-container"><!-- 3D场景画布 --><canvas ref="sceneCanvas" class="scene-canvas"></canvas><!-- AI控制面板 --><div class="ai-controls"><div class="control-section"><h3>🎮 NPC管理</h3><div class="npc-presets"><button v-for="preset in npcPresets" :key="preset.id"@click="spawnNPCPreset(preset)"class="preset-button">{{ preset.name }}</button></div><div class="npc-stats"><div class="stat-item"><span>活跃NPC:</span><span>{{ activeNPCs }}</span></div><div class="stat-item"><span>总NPC:</span><span>{{ totalNPCs }}</span></div><div class="stat-item"><span>帧率:</span><span>{{ currentFPS }} FPS</span></div></div><div class="npc-actions"><button @click="spawnRandomNPC" class="action-button">🎲 生成随机NPC</button><button @click="clearAllNPCs" class="action-button danger">🗑️ 清空所有NPC</button></div></div><div class="control-section"><h3>🧭 导航系统</h3><div class="navigation-controls"><div class="control-group"><label>寻路算法</label><select v-model="pathfindingAlgorithm" class="algorithm-select"><option value="astar">A* 算法</option><option value="dijkstra">Dijkstra 算法</option><option value="navmesh">导航网格</option><option value="potential">势场法</option></select></div><div class="control-group"><label>路径平滑: {{ pathSmoothing }}</label><input type="range" v-model="pathSmoothing" min="0" max="1" step="0.1"></div><div class="control-group"><label>避障强度: {{ avoidanceStrength }}</label><input type="range" v-model="avoidanceStrength" min="0" max="2" step="0.1"></div></div><div class="environment-controls"><div class="control-group"><label>显示导航网格</label><input type="checkbox" v-model="showNavMesh"></div><div class="control-group"><label>显示路径线</label><input type="checkbox" v-model="showPathLines"></div><div class="control-group"><label>显示感知范围</label><input type="checkbox" v-model="showPerceptionRange"></div></div></div><div class="control-section"><h3>🤖 AI行为系统</h3><div class="behavior-controls"><div class="control-group"><label>AI类型</label><select v-model="selectedAIType" class="ai-type-select"><option value="civilian">平民</option><option value="guard">守卫</option><option value="merchant">商人</option><option value="animal">动物</option><option value="monster">怪物</option></select></div><div class="control-group"><label>攻击性: {{ aggressionLevel }}</label><input type="range" v-model="aggressionLevel" min="0" max="1" step="0.1"></div><div class="control-group"><label>社交性: {{ sociabilityLevel }}</label><input type="range" v-model="sociabilityLevel" min="0" max="1" step="0.1"></div></div><div class="ai-settings"><div class="control-group"><label>启用机器学习</label><input type="checkbox" v-model="mlEnabled"></div><div class="control-group"><label>群体行为</label><input type="checkbox" v-model="flockingEnabled"></div><div class="control-group"><label>情感模拟</label><input type="checkbox" v-model="emotionSimulation"></div></div></div><div class="control-section"><h3>👁️ 感知系统</h3><div class="perception-controls"><div class="control-group"><label>视觉范围: {{ visionRange }}</label><input type="range" v-model="visionRange" min="5" max="50" step="1"></div><div class="control-group"><label>听觉范围: {{ hearingRange }}</label><input type="range" v-model="hearingRange" min="1" max="20" step="1"></div><div class="control-group"><label>记忆时长: {{ memoryDuration }}</label><input type="range" v-model="memoryDuration" min="5" max="60" step="5"></div></div><div class="debug-controls"><div class="control-group"><label>显示视觉锥</label><input type="checkbox" v-model="showVisionCones"></div><div class="control-group"><label>显示听觉范围</label><input type="checkbox" v-model="showHearingRange"></div><div class="control-group"><label>显示目标</label><input type="checkbox" v-model="showTargets"></div></div></div><div class="control-section"><h3>📊 性能监控</h3><div class="performance-stats"><div class="stat-item"><span>AI计算时间:</span><span>{{ aiComputeTime.toFixed(2) }}ms</span></div><div class="stat-item"><span>路径查找:</span><span>{{ pathfindingTime.toFixed(2) }}ms</span></div><div class="stat-item"><span>感知检测:</span><span>{{ perceptionTime.toFixed(2) }}ms</span></div><div class="stat-item"><span>行为更新:</span><span>{{ behaviorUpdateTime.toFixed(2) }}ms</span></div></div><div class="memory-stats"><div class="stat-item"><span>导航网格:</span><span>{{ formatMemory(navMeshMemory) }}</span></div><div class="stat-item"><span>行为树:</span><span>{{ formatMemory(behaviorTreeMemory) }}</span></div><div class="stat-item"><span>感知数据:</span><span>{{ formatMemory(perceptionMemory) }}</span></div></div></div></div><!-- 场景控制 --><div class="scene-controls"><button @click="toggleEnvironment" class="scene-button">{{ showEnvironment ? '隐藏环境' : '显示环境' }}</button><button @click="resetScene" class="scene-button">🔄 重置场景</button><button @click="togglePause" class="scene-button">{{ isPaused ? '▶️ 继续' : '⏸️ 暂停' }}</button></div><!-- NPC信息面板 --><div class="npc-info-panel"><div class="info-header"><h4>NPC信息</h4><span class="close-button" @click="closeNPCInfo">×</span></div><div v-if="selectedNPC" class="info-content"><div class="npc-basic-info"><div class="info-row"><span>名称:</span><span>{{ selectedNPC.name }}</span></div><div class="info-row"><span>类型:</span><span>{{ selectedNPC.type }}</span></div><div class="info-row"><span>状态:</span><span>{{ selectedNPC.state }}</span></div><div class="info-row"><span>健康值:</span><span>{{ selectedNPC.health }}/100</span></div></div><div class="npc-advanced-info"><div class="info-section"><h5>行为状态</h5><div class="behavior-stats"><div class="stat-bar"><label>攻击性</label><div class="bar-container"><div class="bar-fill" :style="{ width: selectedNPC.aggression * 100 + '%' }"></div></div></div><div class="stat-bar"><label>恐惧度</label><div class="bar-container"><div class="bar-fill" :style="{ width: selectedNPC.fear * 100 + '%' }"></div></div></div><div class="stat-bar"><label>好奇心</label><div class="bar-container"><div class="bar-fill" :style="{ width: selectedNPC.curiosity * 100 + '%' }"></div></div></div></div></div><div class="info-section"><h5>当前目标</h5><div class="target-info"><div>{{ selectedNPC.currentTarget || '无' }}</div><div v-if="selectedNPC.targetPosition" class="target-position">位置: ({{ selectedNPC.targetPosition.x.toFixed(1) }}, {{ selectedNPC.targetPosition.y.toFixed(1) }})</div></div></div></div></div><div v-else class="no-selection">点击NPC查看详细信息</div></div><!-- 加载界面 --><div v-if="isLoading" class="loading-overlay"><div class="loading-content"><div class="ai-brain-loader"><div class="neuron"></div><div class="neuron"></div><div class="neuron"></div><div class="neuron"></div><div class="neuron"></div></div><h3>初始化AI系统...</h3><div class="loading-progress"><div class="progress-bar"><div class="progress-fill" :style="loadingProgressStyle"></div></div><span>{{ loadingMessage }}</span></div><div class="loading-details"><span>已加载: {{ loadedSystems }}/{{ totalSystems }} 系统</span><span>内存使用: {{ formatMemory(loadingMemory) }}</span></div></div></div><!-- 调试信息 --><div class="debug-info"><div class="debug-panel"><div class="debug-row"><span>活跃行为树:</span><span>{{ activeBehaviorTrees }}</span></div><div class="debug-row"><span>路径计算:</span><span>{{ pathCalculations }}/帧</span></div><div class="debug-row"><span>感知检测:</span><span>{{ perceptionChecks }}/帧</span></div><div class="debug-row"><span>碰撞检测:</span><span>{{ collisionChecks }}/帧</span></div></div></div></div>
</template><script>
import { onMounted, onUnmounted, ref, reactive, computed } from 'vue';
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';// A* 路径查找算法
class AStarPathfinder {constructor(navMesh) {this.navMesh = navMesh;this.openSet = new Set();this.closedSet = new Set();this.gScore = new Map();this.fScore = new Map();this.cameFrom = new Map();}// 查找路径findPath(start, goal) {const startTime = performance.now();this.openSet.clear();this.closedSet.clear();this.gScore.clear();this.fScore.clear();this.cameFrom.clear();this.openSet.add(start);this.gScore.set(start, 0);this.fScore.set(start, this.heuristic(start, goal));while (this.openSet.size > 0) {const current = this.getLowestFScore();if (current.equals(goal)) {const path = this.reconstructPath(current);const time = performance.now() - startTime;return { path, time, nodesExplored: this.closedSet.size };}this.openSet.delete(current);this.closedSet.add(current);for (const neighbor of this.getNeighbors(current)) {if (this.closedSet.has(neighbor)) continue;const tentativeGScore = this.gScore.get(current) + this.distance(current, neighbor);if (!this.openSet.has(neighbor) || tentativeGScore < this.gScore.get(neighbor)) {this.cameFrom.set(neighbor, current);this.gScore.set(neighbor, tentativeGScore);this.fScore.set(neighbor, tentativeGScore + this.heuristic(neighbor, goal));if (!this.openSet.has(neighbor)) {this.openSet.add(neighbor);}}}}return { path: [], time: performance.now() - startTime, nodesExplored: this.closedSet.size };}// 获取最低f值的节点getLowestFScore() {let lowest = null;let lowestScore = Infinity;for (const node of this.openSet) {const score = this.fScore.get(node);if (score < lowestScore) {lowestScore = score;lowest = node;}}return lowest;}// 重构路径reconstructPath(current) {const path = [current];while (this.cameFrom.has(current)) {current = this.cameFrom.get(current);path.unshift(current);}return path;}// 启发式函数(曼哈顿距离)heuristic(a, b) {return Math.abs(a.x - b.x) + Math.abs(a.y - b.y) + Math.abs(a.z - b.z);}// 距离计算distance(a, b) {return a.distanceTo(b);}// 获取邻居节点getNeighbors(node) {const neighbors = [];const directions = [new THREE.Vector3(1, 0, 0), new THREE.Vector3(-1, 0, 0),new THREE.Vector3(0, 0, 1), new THREE.Vector3(0, 0, -1),new THREE.Vector3(1, 0, 1), new THREE.Vector3(-1, 0, 1),new THREE.Vector3(1, 0, -1), new THREE.Vector3(-1, 0, -1)];for (const dir of directions) {const neighbor = node.clone().add(dir);if (this.isWalkable(neighbor)) {neighbors.push(neighbor);}}return neighbors;}// 检查是否可行走isWalkable(position) {// 简化实现 - 实际应该检查导航网格return position.y >= 0 && position.y <= 1;}
}// 行为树系统
class BehaviorTree {constructor() {this.root = null;this.blackboard = new Map();this.isRunning = false;}// 更新行为树update(deltaTime) {if (this.root && this.isRunning) {return this.root.execute(this.blackboard, deltaTime);}return 'FAILURE';}// 设置根节点setRoot(node) {this.root = node;}// 开始运行start() {this.isRunning = true;}// 停止运行stop() {this.isRunning = false;}// 设置黑板值setValue(key, value) {this.blackboard.set(key, value);}// 获取黑板值getValue(key) {return this.blackboard.get(key);}
}// 行为树节点基类
class BTNode {constructor(name = 'BTNode') {this.name = name;this.children = [];}addChild(node) {this.children.push(node);return this;}execute(blackboard, deltaTime) {return 'FAILURE';}
}// 选择器节点(OR逻辑)
class Selector extends BTNode {execute(blackboard, deltaTime) {for (const child of this.children) {const result = child.execute(blackboard, deltaTime);if (result === 'SUCCESS' || result === 'RUNNING') {return result;}}return 'FAILURE';}
}// 序列节点(AND逻辑)
class Sequence extends BTNode {execute(blackboard, deltaTime) {for (const child of this.children) {const result = child.execute(blackboard, deltaTime);if (result === 'FAILURE') {return 'FAILURE';} else if (result === 'RUNNING') {return 'RUNNING';}}return 'SUCCESS';}
}// 条件节点
class Condition extends BTNode {constructor(conditionFn, name = 'Condition') {super(name);this.conditionFn = conditionFn;}execute(blackboard, deltaTime) {return this.conditionFn(blackboard) ? 'SUCCESS' : 'FAILURE';}
}// 动作节点
class Action extends BTNode {constructor(actionFn, name = 'Action') {super(name);this.actionFn = actionFn;}execute(blackboard, deltaTime) {return this.actionFn(blackboard, deltaTime);}
}// 装饰器节点基类
class Decorator extends BTNode {constructor(child, name = 'Decorator') {super(name);if (child) {this.children.push(child);}}
}// 重复装饰器
class Repeater extends Decorator {constructor(count = Infinity, child = null) {super(child, 'Repeater');this.count = count;this.currentCount = 0;}execute(blackboard, deltaTime) {if (this.children.length === 0) return 'FAILURE';const child = this.children[0];const result = child.execute(blackboard, deltaTime);if (result === 'RUNNING') {return 'RUNNING';}this.currentCount++;if (this.currentCount >= this.count) {this.currentCount = 0;return 'SUCCESS';}return 'RUNNING';}
}// 感知系统
class PerceptionSystem {constructor(npc, options = {}) {this.npc = npc;this.options = {visionRange: 15,hearingRange: 8,visionAngle: Math.PI / 3, // 120度视野memoryDuration: 30, // 30秒记忆...options};this.memory = new Map();this.stimuli = new Set();}// 更新感知系统update(scene, deltaTime) {const startTime = performance.now();let detectedCount = 0;// 清理过期记忆this.cleanupMemory(deltaTime);// 检测视觉刺激detectedCount += this.detectVisualStimuli(scene);// 检测听觉刺激detectedCount += this.detectAuditoryStimuli(scene);// 处理当前刺激this.processStimuli();this.lastUpdateTime = performance.now() - startTime;return detectedCount;}// 检测视觉刺激detectVisualStimuli(scene) {let detected = 0;const npcPos = this.npc.getWorldPosition(new THREE.Vector3());const npcForward = this.npc.getWorldDirection(new THREE.Vector3());scene.traverse((object) => {if (object !== this.npc && this.isPerceivable(object)) {const objectPos = object.getWorldPosition(new THREE.Vector3());const toObject = new THREE.Vector3().subVectors(objectPos, npcPos);const distance = toObject.length();// 检查距离if (distance > this.options.visionRange) return;// 检查视野角度const angle = npcForward.angleTo(toObject.normalize());if (angle > this.options.visionAngle / 2) return;// 光线投射检查遮挡if (!this.hasLineOfSight(npcPos, objectPos, scene)) return;// 记录感知this.recordStimulus('visual', object, distance);detected++;}});return detected;}// 检测听觉刺激detectAuditoryStimuli(scene) {let detected = 0;const npcPos = this.npc.getWorldPosition(new THREE.Vector3());// 简化实现 - 实际应该从声音系统获取scene.traverse((object) => {if (object !== this.npc && this.isAudible(object)) {const objectPos = object.getWorldPosition(new THREE.Vector3());const distance = objectPos.distanceTo(npcPos);if (distance <= this.options.hearingRange) {this.recordStimulus('auditory', object, distance);detected++;}}});return detected;}// 检查是否可感知isPerceivable(object) {return object.isNPC || object.isPlayer || object.isInteractive;}// 检查是否可听到isAudible(object) {return object.isMakingSound && object.soundIntensity > 0;}// 检查视线hasLineOfSight(from, to, scene) {const direction = new THREE.Vector3().subVectors(to, from).normalize();const raycaster = new THREE.Raycaster(from, direction, 0.1, this.options.visionRange);const intersects = raycaster.intersectObjects(scene.children, true);for (const intersect of intersects) {// 忽略自己和其他NPC(简化实现)if (intersect.object === this.npc || intersect.object.isNPC) {continue;}// 如果碰到障碍物,返回falseif (intersect.object.isObstacle) {return false;}}return true;}// 记录刺激recordStimulus(type, source, intensity) {const stimulus = {type,source,intensity,position: source.getWorldPosition(new THREE.Vector3()),timestamp: Date.now()};this.stimuli.add(stimulus);this.memory.set(source.uuid, stimulus);}// 处理刺激processStimuli() {for (const stimulus of this.stimuli) {// 根据刺激类型和强度更新NPC状态this.npc.processStimulus(stimulus);}this.stimuli.clear();}// 清理记忆cleanupMemory(deltaTime) {const now = Date.now();const toDelete = [];for (const [key, stimulus] of this.memory) {if (now - stimulus.timestamp > this.options.memoryDuration * 1000) {toDelete.push(key);}}for (const key of toDelete) {this.memory.delete(key);}}// 获取已知目标getKnownTargets() {return Array.from(this.memory.values()).map(stimulus => stimulus.source);}
}// NPC角色类
class NPC {constructor(options = {}) {this.id = THREE.MathUtils.generateUUID();this.name = options.name || `NPC_${this.id.slice(0, 8)}`;this.type = options.type || 'civilian';this.health = options.health || 100;this.speed = options.speed || 2;// AI属性this.aggression = options.aggression || 0.1;this.fear = options.fear || 0.3;this.curiosity = options.curiosity || 0.5;this.sociability = options.sociability || 0.7;// 状态this.state = 'idle';this.currentTarget = null;this.targetPosition = null;this.path = [];this.pathIndex = 0;// 三维对象this.mesh = this.createMesh();this.perceptionSystem = new PerceptionSystem(this.mesh, options.perception);this.behaviorTree = this.createBehaviorTree();// 初始化行为树this.behaviorTree.start();}// 创建网格createMesh() {const geometry = new THREE.CapsuleGeometry(0.5, 1, 4, 8);const material = new THREE.MeshStandardMaterial({ color: this.getColorByType(),roughness: 0.7,metalness: 0.1});const mesh = new THREE.Mesh(geometry, material);mesh.castShadow = true;mesh.receiveShadow = true;mesh.isNPC = true;return mesh;}// 根据类型获取颜色getColorByType() {const colors = {civilian: 0x3498db, // 蓝色guard: 0xe74c3c, // 红色merchant: 0xf39c12, // 橙色animal: 0x27ae60, // 绿色monster: 0x8e44ad // 紫色};return colors[this.type] || 0x95a5a6;}// 创建行为树createBehaviorTree() {const tree = new BehaviorTree();// 根据NPC类型创建不同的行为树switch (this.type) {case 'civilian':tree.setRoot(this.createCivilianBehaviorTree());break;case 'guard':tree.setRoot(this.createGuardBehaviorTree());break;case 'merchant':tree.setRoot(this.createMerchantBehaviorTree());break;case 'animal':tree.setRoot(this.createAnimalBehaviorTree());break;case 'monster':tree.setRoot(this.createMonsterBehaviorTree());break;}return tree;}// 创建平民行为树createCivilianBehaviorTree() {return new Selector('CivilianRoot').addChild(new Sequence('SocialBehavior').addChild(new Condition(bb => bb.get('seeFriend'), 'SeeFriend')).addChild(new Action(this.approachFriend.bind(this), 'ApproachFriend'))).addChild(new Sequence('ExploreBehavior').addChild(new Condition(bb => bb.get('isCurious'), 'IsCurious')).addChild(new Action(this.explore.bind(this), 'Explore'))).addChild(new Sequence('IdleBehavior').addChild(new Condition(bb => true, 'AlwaysTrue')).addChild(new Repeater(Infinity, new Action(this.wander.bind(this), 'Wander'))));}// 创建守卫行为树createGuardBehaviorTree() {return new Selector('GuardRoot').addChild(new Sequence('CombatBehavior').addChild(new Condition(bb => bb.get('seeEnemy'), 'SeeEnemy')).addChild(new Action(this.attackEnemy.bind(this), 'AttackEnemy'))).addChild(new Sequence('PatrolBehavior').addChild(new Condition(bb => !bb.get('seeEnemy'), 'NoEnemy')).addChild(new Action(this.patrol.bind(this), 'Patrol'))).addChild(new Action(this.standGuard.bind(this), 'StandGuard'));}// 更新NPCupdate(deltaTime, scene, pathfinder) {// 更新感知this.perceptionSystem.update(scene, deltaTime);// 更新行为树黑板this.updateBlackboard();// 执行行为树const behaviorResult = this.behaviorTree.update(deltaTime);// 更新移动this.updateMovement(deltaTime, pathfinder);// 更新状态this.updateState(behaviorResult);return behaviorResult;}// 更新黑板updateBlackboard() {const bb = this.behaviorTree.blackboard;// 设置感知数据bb.set('seeFriend', this.checkForFriends());bb.set('seeEnemy', this.checkForEnemies());bb.set('isCurious', Math.random() < this.curiosity);bb.set('health', this.health);bb.set('state', this.state);}// 检查朋友checkForFriends() {const knownTargets = this.perceptionSystem.getKnownTargets();return knownTargets.some(target => target.isNPC && target !== this.mesh && this.getRelationship(target) > 0.5);}// 检查敌人checkForEnemies() {const knownTargets = this.perceptionSystem.getKnownTargets();return knownTargets.some(target => (!target.isNPC || this.getRelationship(target) < -0.5) &&this.aggression > 0.3);}// 获取关系值getRelationship(otherNPC) {// 简化实现 - 实际应该有更复杂的关系系统if (this.type === 'guard' && otherNPC.type === 'monster') return -1.0;if (this.type === otherNPC.type) return 0.7;return 0.0;}// 更新移动updateMovement(deltaTime, pathfinder) {if (this.targetPosition && this.path.length === 0) {// 需要寻路const result = pathfinder.findPath(this.mesh.position,this.targetPosition);if (result.path.length > 0) {this.path = result.path;this.pathIndex = 0;}}if (this.path.length > 0 && this.pathIndex < this.path.length) {this.followPath(deltaTime);}}// 跟随路径followPath(deltaTime) {const targetPoint = this.path[this.pathIndex];const direction = new THREE.Vector3().subVectors(targetPoint, this.mesh.position).normalize();// 移动this.mesh.position.add(direction.multiplyScalar(this.speed * deltaTime));// 旋转朝向移动方向if (direction.length() > 0.1) {this.mesh.lookAt(this.mesh.position.clone().add(direction));}// 检查是否到达路径点if (this.mesh.position.distanceTo(targetPoint) < 0.5) {this.pathIndex++;if (this.pathIndex >= this.path.length) {this.path = [];this.pathIndex = 0;this.targetPosition = null;}}}// 更新状态updateState(behaviorResult) {// 根据行为结果更新状态this.state = behaviorResult.toLowerCase();}// 行为方法wander(blackboard, deltaTime) {if (!this.targetPosition) {// 随机选择 wander 目标const angle = Math.random() * Math.PI * 2;const distance = 5 + Math.random() * 10;this.targetPosition = new THREE.Vector3(this.mesh.position.x + Math.cos(angle) * distance,0,this.mesh.position.z + Math.sin(angle) * distance);}return 'RUNNING';}approachFriend(blackboard, deltaTime) {// 简化实现return 'RUNNING';}explore(blackboard, deltaTime) {// 简化实现return 'RUNNING';}attackEnemy(blackboard, deltaTime) {// 简化实现return 'RUNNING';}patrol(blackboard, deltaTime) {// 简化实现return 'RUNNING';}standGuard(blackboard, deltaTime) {// 简化实现return 'RUNNING';}// 处理刺激processStimulus(stimulus) {// 根据刺激更新内部状态switch (stimulus.type) {case 'visual':if (stimulus.source.isNPC) {const relationship = this.getRelationship(stimulus.source);if (relationship < -0.5) {this.fear = Math.min(1.0, this.fear + 0.1);}}break;case 'auditory':this.curiosity = Math.min(1.0, this.curiosity + 0.05);break;}}// 获取世界位置getWorldPosition(target) {return this.mesh.getWorldPosition(target);}
}// NPC管理系统
class NPCManager {constructor(scene) {this.scene = scene;this.npcs = new Map();this.pathfinder = new AStarPathfinder();this.lastSpawnTime = 0;this.spawnInterval = 2.0; // 秒}// 生成NPCspawnNPC(options = {}) {const npc = new NPC(options);this.npcs.set(npc.id, npc);this.scene.add(npc.mesh);return npc;}// 移除NPCremoveNPC(npcId) {const npc = this.npcs.get(npcId);if (npc) {this.scene.remove(npc.mesh);this.npcs.delete(npcId);}}// 清空所有NPCclearAll() {for (const npc of this.npcs.values()) {this.scene.remove(npc.mesh);}this.npcs.clear();}// 更新所有NPCupdate(deltaTime) {let totalComputeTime = 0;let activeNPCs = 0;for (const npc of this.npcs.values()) {const startTime = performance.now();npc.update(deltaTime, this.scene, this.pathfinder);totalComputeTime += performance.now() - startTime;activeNPCs++;}return {computeTime: totalComputeTime,activeNPCs: activeNPCs,totalNPCs: this.npcs.size};}// 获取NPC数量getNPCCount() {return this.npcs.size;}// 获取活跃NPCgetActiveNPCs() {return Array.from(this.npcs.values());}
}export default {name: 'AINPCSystem',setup() {const sceneCanvas = ref(null);const pathfindingAlgorithm = ref('astar');const pathSmoothing = ref(0.5);const avoidanceStrength = ref(1.0);const showNavMesh = ref(false);const showPathLines = ref(true);const showPerceptionRange = ref(true);const selectedAIType = ref('civilian');const aggressionLevel = ref(0.3);const sociabilityLevel = ref(0.7);const mlEnabled = ref(false);const flockingEnabled = ref(true);const emotionSimulation = ref(true);const visionRange = ref(15);const hearingRange = ref(8);const memoryDuration = ref(30);const showVisionCones = ref(true);const showHearingRange = ref(false);const showTargets = ref(true);const aiComputeTime = ref(0);const pathfindingTime = ref(0);const perceptionTime = ref(0);const behaviorUpdateTime = ref(0);const activeNPCs = ref(0);const totalNPCs = ref(0);const currentFPS = ref(0);const navMeshMemory = ref(0);const behaviorTreeMemory = ref(0);const perceptionMemory = ref(0);const activeBehaviorTrees = ref(0);const pathCalculations = ref(0);const perceptionChecks = ref(0);const collisionChecks = ref(0);const showEnvironment = ref(true);const isPaused = ref(false);const selectedNPC = ref(null);const isLoading = ref(true);const loadingMessage = ref('初始化AI系统...');const loadedSystems = ref(0);const totalSystems = ref(5);const loadingMemory = ref(0);const npcPresets = [{ id: 'civilian_group', name: '平民群体', type: 'civilian', count: 5 },{ id: 'guard_patrol', name: '守卫巡逻', type: 'guard', count: 3 },{ id: 'merchant_camp', name: '商人营地', type: 'merchant', count: 2 },{ id: 'animal_herd', name: '动物群', type: 'animal', count: 8 },{ id: 'monster_pack', name: '怪物群', type: 'monster', count: 4 }];let scene, camera, renderer, controls, npcManager;let clock, stats;let frameCount = 0;let lastFpsUpdate = 0;let raycaster, mouse;// 初始化场景const initScene = async () => {// 创建场景scene = new THREE.Scene();scene.background = new THREE.Color(0x87ceeb);// 创建相机camera = new THREE.PerspectiveCamera(75,window.innerWidth / window.innerHeight,0.1,1000);camera.position.set(20, 15, 20);// 创建渲染器renderer = new THREE.WebGLRenderer({canvas: sceneCanvas.value,antialias: true});renderer.setSize(window.innerWidth, window.innerHeight);renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));renderer.shadowMap.enabled = true;renderer.shadowMap.type = THREE.PCFSoftShadowMap;// 添加控制器controls = new OrbitControls(camera, renderer.domElement);controls.enableDamping = true;// 设置光线投射raycaster = new THREE.Raycaster();mouse = new THREE.Vector2();// 创建环境loadingMessage.value = '创建环境...';await createEnvironment();// 初始化NPC管理器loadingMessage.value = '初始化NPC系统...';await setupNPCManager();// 设置事件监听setupEventListeners();isLoading.value = false;// 启动渲染循环clock = new THREE.Clock();animate();};// 创建环境const createEnvironment = async () => {// 添加光照const ambientLight = new THREE.AmbientLight(0x404040, 0.6);scene.add(ambientLight);const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);directionalLight.position.set(50, 50, 25);directionalLight.castShadow = true;scene.add(directionalLight);// 创建地面const groundGeometry = new THREE.PlaneGeometry(100, 100);const groundMaterial = new THREE.MeshStandardMaterial({ color: 0x3a9d23,roughness: 0.8,metalness: 0.2});const ground = new THREE.Mesh(groundGeometry, groundMaterial);ground.rotation.x = -Math.PI / 2;ground.receiveShadow = true;scene.add(ground);// 添加障碍物createObstacles();// 添加网格辅助const gridHelper = new THREE.GridHelper(100, 20);scene.add(gridHelper);};// 创建障碍物const createObstacles = () => {const obstacleCount = 15;for (let i = 0; i < obstacleCount; i++) {const size = 2 + Math.random() * 3;const geometry = new THREE.BoxGeometry(size, size, size);const material = new THREE.MeshStandardMaterial({ color: 0x795548,roughness: 0.9});const obstacle = new THREE.Mesh(geometry, material);obstacle.position.set((Math.random() - 0.5) * 80,size / 2,(Math.random() - 0.5) * 80);obstacle.castShadow = true;obstacle.receiveShadow = true;obstacle.isObstacle = true;scene.add(obstacle);}};// 初始化NPC管理器const setupNPCManager = async () => {npcManager = new NPCManager(scene);// 生成一些初始NPCspawnInitialNPCs();loadedSystems.value = totalSystems.value;};// 生成初始NPCconst spawnInitialNPCs = () => {// 生成一些测试NPCfor (let i = 0; i < 8; i++) {const type = ['civilian', 'guard', 'merchant', 'animal'][i % 4];npcManager.spawnNPC({name: `NPC_${i}`,type: type,position: new THREE.Vector3((Math.random() - 0.5) * 40,0,(Math.random() - 0.5) * 40)});}};// 设置事件监听const setupEventListeners = () => {// 鼠标点击选择NPCrenderer.domElement.addEventListener('click', onCanvasClick);};// 画布点击事件const onCanvasClick = (event) => {// 计算鼠标位置const rect = renderer.domElement.getBoundingClientRect();mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;// 更新光线投射raycaster.setFromCamera(mouse, camera);// 检测与NPC的相交const npcMeshes = Array.from(npcManager.npcs.values()).map(npc => npc.mesh);const intersects = raycaster.intersectObjects(npcMeshes);if (intersects.length > 0) {const clickedMesh = intersects[0].object;// 找到对应的NPCfor (const npc of npcManager.npcs.values()) {if (npc.mesh === clickedMesh) {selectedNPC.value = {name: npc.name,type: npc.type,state: npc.state,health: npc.health,aggression: npc.aggression,fear: npc.fear,curiosity: npc.curiosity,currentTarget: npc.currentTarget,targetPosition: npc.targetPosition};break;}}} else {selectedNPC.value = null;}};// 生成NPC预设const spawnNPCPreset = (preset) => {for (let i = 0; i < preset.count; i++) {npcManager.spawnNPC({name: `${preset.type}_${i}`,type: preset.type,position: new THREE.Vector3((Math.random() - 0.5) * 30,0,(Math.random() - 0.5) * 30),aggression: preset.type === 'monster' ? 0.8 : preset.type === 'guard' ? 0.6 : 0.1});}};// 生成随机NPCconst spawnRandomNPC = () => {const types = ['civilian', 'guard', 'merchant', 'animal', 'monster'];const type = types[Math.floor(Math.random() * types.length)];npcManager.spawnNPC({name: `Random_${Date.now()}`,type: type,position: new THREE.Vector3((Math.random() - 0.5) * 40,0,(Math.random() - 0.5) * 40),aggression: type === 'monster' ? 0.9 : type === 'guard' ? 0.7 : type === 'animal' ? 0.3 : 0.1});};// 清空所有NPCconst clearAllNPCs = () => {npcManager.clearAll();selectedNPC.value = null;};// 切换环境显示const toggleEnvironment = () => {showEnvironment.value = !showEnvironment.value;// 实际实现应该切换环境物体的可见性};// 重置场景const resetScene = () => {clearAllNPCs();spawnInitialNPCs();};// 切换暂停const togglePause = () => {isPaused.value = !isPaused.value;};// 关闭NPC信息const closeNPCInfo = () => {selectedNPC.value = null;};// 动画循环const animate = () => {requestAnimationFrame(animate);const deltaTime = clock.getDelta();if (!isPaused.value) {// 更新控制器controls.update();// 更新NPC管理器const npcStats = npcManager.update(deltaTime);aiComputeTime.value = npcStats.computeTime;activeNPCs.value = npcStats.activeNPCs;totalNPCs.value = npcStats.totalNPCs;// 更新调试信息updateDebugInfo();}// 渲染场景renderer.render(scene, camera);// 更新性能统计updatePerformanceStats(deltaTime);};// 更新调试信息const updateDebugInfo = () => {// 模拟一些调试数据activeBehaviorTrees.value = activeNPCs.value;pathCalculations.value = Math.floor(Math.random() * 20) + 5;perceptionChecks.value = Math.floor(Math.random() * 50) + 20;collisionChecks.value = Math.floor(Math.random() * 30) + 10;// 更新内存统计navMeshMemory.value = 1024 * 1024; // 1MBbehaviorTreeMemory.value = activeNPCs.value * 64 * 1024; // 每个NPC 64KBperceptionMemory.value = activeNPCs.value * 32 * 1024; // 每个NPC 32KB};// 更新性能统计const updatePerformanceStats = (deltaTime) => {frameCount++;lastFpsUpdate += deltaTime;if (lastFpsUpdate >= 1.0) {currentFPS.value = Math.round(frameCount / lastFpsUpdate);frameCount = 0;lastFpsUpdate = 0;}};// 格式化内存大小const formatMemory = (bytes) => {if (bytes >= 1024 * 1024) {return (bytes / (1024 * 1024)).toFixed(1) + ' MB';} else if (bytes >= 1024) {return (bytes / 1024).toFixed(1) + ' KB';}return bytes + ' B';};// 计算属性const loadingProgressStyle = computed(() => ({width: '100%'}));onMounted(() => {initScene();window.addEventListener('resize', handleResize);});onUnmounted(() => {if (renderer) {renderer.dispose();}window.removeEventListener('resize', handleResize);});const handleResize = () => {if (!camera || !renderer) return;camera.aspect = window.innerWidth / window.innerHeight;camera.updateProjectionMatrix();renderer.setSize(window.innerWidth, window.innerHeight);};return {sceneCanvas,pathfindingAlgorithm,pathSmoothing,avoidanceStrength,showNavMesh,showPathLines,showPerceptionRange,selectedAIType,aggressionLevel,sociabilityLevel,mlEnabled,flockingEnabled,emotionSimulation,visionRange,hearingRange,memoryDuration,showVisionCones,showHearingRange,showTargets,aiComputeTime,pathfindingTime,perceptionTime,behaviorUpdateTime,activeNPCs,totalNPCs,currentFPS,navMeshMemory,behaviorTreeMemory,perceptionMemory,activeBehaviorTrees,pathCalculations,perceptionChecks,collisionChecks,showEnvironment,isPaused,selectedNPC,isLoading,loadingMessage,loadedSystems,totalSystems,loadingMemory,npcPresets,loadingProgressStyle,spawnNPCPreset,spawnRandomNPC,clearAllNPCs,toggleEnvironment,resetScene,togglePause,closeNPCInfo,formatMemory};}
};
</script><style scoped>
.ai-npc-container {width: 100%;height: 100vh;position: relative;background: #000;overflow: hidden;
}.scene-canvas {width: 100%;height: 100%;display: block;
}.ai-controls {position: absolute;top: 20px;right: 20px;width: 350px;background: rgba(0, 0, 0, 0.9);padding: 20px;border-radius: 12px;color: white;backdrop-filter: blur(10px);border: 1px solid rgba(255, 255, 255, 0.1);max-height: 80vh;overflow-y: auto;
}.control-section {margin-bottom: 25px;padding-bottom: 15px;border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}.control-section:last-child {margin-bottom: 0;border-bottom: none;
}.control-section h3 {color: #00ff88;margin-bottom: 15px;font-size: 16px;display: flex;align-items: center;gap: 8px;
}.npc-presets {display: grid;grid-template-columns: 1fr 1fr;gap: 8px;margin-bottom: 15px;
}.preset-button {padding: 8px;border: 1px solid #444;border-radius: 4px;background: rgba(255, 255, 255, 0.05);color: white;cursor: pointer;font-size: 11px;transition: all 0.3s ease;
}.preset-button:hover {border-color: #00ff88;
}.npc-stats {display: flex;flex-direction: column;gap: 8px;margin-bottom: 15px;
}.stat-item {display: flex;justify-content: space-between;align-items: center;padding: 6px 0;font-size: 12px;
}.stat-item span:first-child {color: #ccc;
}.stat-item span:last-child {color: #00ff88;font-weight: bold;
}.npc-actions {display: flex;flex-direction: column;gap: 10px;
}.action-button {padding: 10px;border: none;border-radius: 6px;background: rgba(255, 255, 255, 0.1);color: white;cursor: pointer;font-size: 14px;transition: all 0.3s ease;display: flex;align-items: center;justify-content: center;gap: 8px;
}.action-button:hover {transform: translateY(-2px);box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
}.action-button.danger {background: linear-gradient(45deg, #ff6b6b, #ee5a24);
}.control-group {margin-bottom: 15px;
}.control-group label {display: flex;justify-content: space-between;align-items: center;margin-bottom: 8px;color: #ccc;font-size: 14px;
}.control-group input[type="range"] {width: 100%;height: 6px;background: #444;border-radius: 3px;outline: none;opacity: 0.7;transition: opacity 0.2s;
}.control-group input[type="range"]:hover {opacity: 1;
}.control-group input[type="range"]::-webkit-slider-thumb {appearance: none;width: 16px;height: 16px;border-radius: 50%;background: #00ff88;cursor: pointer;
}.control-group input[type="checkbox"] {width: 18px;height: 18px;accent-color: #00ff88;
}.algorithm-select,
.ai-type-select {width: 100%;padding: 8px;border: 1px solid #444;border-radius: 4px;background: rgba(255, 255, 255, 0.1);color: white;font-size: 14px;
}.navigation-controls,
.behavior-controls,
.perception-controls {margin-bottom: 15px;
}.environment-controls,
.ai-settings,
.debug-controls {display: grid;grid-template-columns: 1fr 1fr;gap: 10px;
}.performance-stats,
.memory-stats {display: flex;flex-direction: column;gap: 8px;margin-bottom: 15px;
}.scene-controls {position: absolute;top: 20px;left: 20px;display: flex;gap: 10px;
}.scene-button {padding: 10px 15px;border: none;border-radius: 6px;background: rgba(255, 255, 255, 0.1);color: white;cursor: pointer;font-size: 12px;transition: background 0.3s;
}.scene-button:hover {background: rgba(255, 255, 255, 0.2);
}.npc-info-panel {position: absolute;bottom: 20px;left: 20px;width: 300px;background: rgba(0, 0, 0, 0.9);border-radius: 8px;color: white;backdrop-filter: blur(10px);border: 1px solid rgba(255, 255, 255, 0.1);
}.info-header {display: flex;justify-content: space-between;align-items: center;padding: 15px;border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}.info-header h4 {margin: 0;color: #00ff88;
}.close-button {cursor: pointer;font-size: 20px;color: #ccc;
}.close-button:hover {color: white;
}.info-content {padding: 15px;
}.no-selection {padding: 30px;text-align: center;color: #ccc;
}.npc-basic-info {margin-bottom: 20px;
}.info-row {display: flex;justify-content: space-between;align-items: center;padding: 5px 0;font-size: 14px;
}.info-section {margin-bottom: 15px;
}.info-section h5 {margin: 0 0 10px 0;color: #00aaff;font-size: 14px;
}.behavior-stats {display: flex;flex-direction: column;gap: 8px;
}.stat-bar {display: flex;align-items: center;gap: 10px;
}.stat-bar label {width: 60px;font-size: 12px;color: #ccc;
}.bar-container {flex: 1;height: 6px;background: rgba(255, 255, 255, 0.2);border-radius: 3px;overflow: hidden;
}.bar-fill {height: 100%;background: linear-gradient(90deg, #00ff88, #00aaff);border-radius: 3px;transition: width 0.3s ease;
}.target-info {font-size: 12px;color: #ccc;
}.target-position {margin-top: 5px;font-size: 11px;color: #888;
}.loading-overlay {position: absolute;top: 0;left: 0;width: 100%;height: 100%;background: linear-gradient(135deg, #1a2a6c, #b21f1f, #fdbb2d);display: flex;justify-content: center;align-items: center;z-index: 1000;
}.loading-content {text-align: center;color: white;
}.ai-brain-loader {display: flex;justify-content: center;align-items: center;gap: 15px;margin-bottom: 30px;height: 80px;
}.neuron {width: 12px;height: 12px;background: #00ff88;border-radius: 50%;animation: neuronPulse 1.5s infinite ease-in-out;
}.neuron:nth-child(1) { animation-delay: 0s; }
.neuron:nth-child(2) { animation-delay: 0.3s; }
.neuron:nth-child(3) { animation-delay: 0.6s; }
.neuron:nth-child(4) { animation-delay: 0.9s; }
.neuron:nth-child(5) { animation-delay: 1.2s; }.loading-content h3 {margin-bottom: 20px;color: white;font-size: 20px;
}.loading-progress {width: 300px;margin: 0 auto 15px;
}.progress-bar {width: 100%;height: 6px;background: rgba(255, 255, 255, 0.2);border-radius: 3px;overflow: hidden;margin-bottom: 10px;
}.progress-fill {height: 100%;background: linear-gradient(90deg, #00ff88, #00aaff);border-radius: 3px;transition: width 0.3s ease;
}.loading-details {display: flex;justify-content: space-between;font-size: 12px;color: #ccc;
}.debug-info {position: absolute;bottom: 20px;right: 20px;
}.debug-panel {background: rgba(0, 0, 0, 0.7);padding: 10px;border-radius: 6px;backdrop-filter: blur(10px);
}.debug-row {display: flex;justify-content: space-between;align-items: center;font-size: 11px;color: #ccc;margin-bottom: 4px;
}.debug-row:last-child {margin-bottom: 0;
}@keyframes neuronPulse {0%, 100% {transform: scale(1);opacity: 0.7;box-shadow: 0 0 10px rgba(0, 255, 136, 0.5);}50% {transform: scale(1.5);opacity: 1;box-shadow: 0 0 20px rgba(0, 255, 136, 0.8);}
}/* 响应式设计 */
@media (max-width: 768px) {.ai-controls {width: 300px;right: 10px;top: 10px;padding: 15px;}.npc-presets {grid-template-columns: 1fr;}.environment-controls,.ai-settings,.debug-controls {grid-template-columns: 1fr;}.npc-info-panel {left: 10px;bottom: 10px;width: 280px;}.scene-controls {left: 10px;top: 10px;}.debug-info {right: 10px;bottom: 10px;}
}
</style>
高级AI特性实现
机器学习行为系统
// 强化学习代理
class RLAgent {constructor(npc, options = {}) {this.npc = npc;this.options = {learningRate: 0.1,discountFactor: 0.9,explorationRate: 0.3,...options};this.qTable = new Map();this.stateHistory = [];this.lastState = null;this.lastAction = null;}// 选择动作chooseAction(state) {// ε-贪婪策略if (Math.random() < this.options.explorationRate) {return this.exploreAction(state);} else {return this.exploitAction(state);}}// 探索动作exploreAction(state) {const actions = this.getAvailableActions(state);return actions[Math.floor(Math.random() * actions.length)];}// 利用动作exploitAction(state) {const stateKey = this.getStateKey(state);const qValues = this.qTable.get(stateKey) || {};let bestAction = null;let bestValue = -Infinity;for (const [action, value] of Object.entries(qValues)) {if (value > bestValue) {bestValue = value;bestAction = action;}}return bestAction || this.exploreAction(state);}// 更新Q值updateQValue(state, action, reward, nextState) {const stateKey = this.getStateKey(state);const nextStateKey = this.getStateKey(nextState);// 初始化Q值if (!this.qTable.has(stateKey)) {this.qTable.set(stateKey, {});}const currentQ = this.qTable.get(stateKey)[action] || 0;const nextMaxQ = this.getMaxQValue(nextStateKey);// Q学习更新公式const newQ = currentQ + this.options.learningRate * (reward + this.options.discountFactor * nextMaxQ - currentQ);this.qTable.get(stateKey)[action] = newQ;}// 获取最大Q值getMaxQValue(stateKey) {const qValues = this.qTable.get(stateKey);if (!qValues) return 0;return Math.max(...Object.values(qValues));}// 获取可用动作getAvailableActions(state) {const actions = ['move', 'wait', 'interact', 'flee', 'attack'];// 根据状态过滤不可用动作if (state.health < 30) {return actions.filter(a => a !== 'attack');}if (!state.nearEnemy) {return actions.filter(a => a !== 'attack' && a !== 'flee');}return actions;}// 获取状态键getStateKey(state) {return JSON.stringify({health: Math.floor(state.health / 10) * 10, // 离散化nearEnemy: state.nearEnemy,hasTarget: state.hasTarget,environment: state.environment});}// 计算奖励calculateReward(oldState, action, newState) {let reward = 0;// 健康值奖励if (newState.health > oldState.health) reward += 10;if (newState.health < oldState.health) reward -= 20;// 目标达成奖励if (action === 'interact' && newState.hasTarget) reward += 15;// 生存奖励if (newState.health > 0) reward += 1;// 危险惩罚if (newState.nearEnemy && action !== 'flee' && action !== 'attack') {reward -= 10;}return reward;}// 更新代理update(deltaTime) {const currentState = this.getCurrentState();if (this.lastState && this.lastAction) {const reward = this.calculateReward(this.lastState, this.lastAction, currentState);this.updateQValue(this.lastState, this.lastAction, reward, currentState);}const action = this.chooseAction(currentState);this.executeAction(action);this.lastState = currentState;this.lastAction = action;}// 获取当前状态getCurrentState() {return {health: this.npc.health,nearEnemy: this.npc.perceptionSystem.getKnownTargets().some(t => this.npc.getRelationship(t) < -0.5),hasTarget: !!this.npc.currentTarget,environment: this.getEnvironmentState()};}// 获取环境状态getEnvironmentState() {// 简化实现return 'normal';}// 执行动作executeAction(action) {switch (action) {case 'move':this.npc.wander();break;case 'wait':// 等待break;case 'interact':this.npc.interactWithTarget();break;case 'flee':this.npc.fleeFromDanger();break;case 'attack':this.npc.attackEnemy();break;}}
}// 群体行为系统
class FlockingSystem {constructor(npcs, options = {}) {this.npcs = npcs;this.options = {separationWeight: 1.5,alignmentWeight: 1.0,cohesionWeight: 1.0,separationDistance: 2.0,neighborDistance: 5.0,maxSpeed: 3.0,...options};}// 更新群体行为update(deltaTime) {for (const npc of this.npcs) {if (npc.type === 'animal' || npc.type === 'civilian') {const flockingForce = this.calculateFlockingForce(npc);this.applyFlockingForce(npc, flockingForce, deltaTime);}}}// 计算群体力calculateFlockingForce(npc) {const separation = this.calculateSeparation(npc);const alignment = this.calculateAlignment(npc);const cohesion = this.calculateCohesion(npc);return separation.multiplyScalar(this.options.separationWeight).add(alignment.multiplyScalar(this.options.alignmentWeight)).add(cohesion.multiplyScalar(this.options.cohesionWeight));}// 计算分离力calculateSeparation(npc) {const steer = new THREE.Vector3();let count = 0;for (const other of this.npcs) {if (other !== npc) {const distance = npc.mesh.position.distanceTo(other.mesh.position);if (distance > 0 && distance < this.options.separationDistance) {const diff = new THREE.Vector3().subVectors(npc.mesh.position, other.mesh.position).normalize().divideScalar(distance); // 权重与距离成反比steer.add(diff);count++;}}}if (count > 0) {steer.divideScalar(count);}return steer.normalize();}// 计算对齐力calculateAlignment(npc) {const sum = new THREE.Vector3();let count = 0;for (const other of this.npcs) {if (other !== npc) {const distance = npc.mesh.position.distanceTo(other.mesh.position);if (distance > 0 && distance < this.options.neighborDistance) {// 获取其他NPC的前向向量const forward = new THREE.Vector3(0, 0, -1).applyQuaternion(other.mesh.quaternion);sum.add(forward);count++;}}}if (count > 0) {sum.divideScalar(count);return sum.normalize();}return new THREE.Vector3();}// 计算内聚力calculateCohesion(npc) {const sum = new THREE.Vector3();let count = 0;for (const other of this.npcs) {if (other !== npc) {const distance = npc.mesh.position.distanceTo(other.mesh.position);if (distance > 0 && distance < this.options.neighborDistance) {sum.add(other.mesh.position);count++;}}}if (count > 0) {sum.divideScalar(count);return new THREE.Vector3().subVectors(sum, npc.mesh.position).normalize();}return new THREE.Vector3();}// 应用群体力applyFlockingForce(npc, force, deltaTime) {// 限制最大速度if (force.length() > this.options.maxSpeed) {force.normalize().multiplyScalar(this.options.maxSpeed);}// 应用力npc.mesh.position.add(force.multiplyScalar(deltaTime));// 更新朝向if (force.length() > 0.1) {npc.mesh.lookAt(npc.mesh.position.clone().add(force));}}
}
注意事项与最佳实践
-
性能优化策略
- 使用空间分割优化感知检测
- 限制每帧的路径计算数量
- 实现行为树的层级更新
- 使用对象池重用NPC对象
-
AI设计原则
- 保持行为可预测且合理
- 实现清晰的难度曲线
- 提供玩家反馈机制
- 平衡挑战性和公平性
-
调试与测试
- 实现可视化调试工具
- 记录AI决策过程
- 创建自动化测试场景
- 监控性能指标
下一节预告
第37节:移动端优化与触控交互
将深入探索移动端3D应用的性能优化技术,包括:触控交互设计、渲染性能优化、内存管理策略、跨平台适配,打造流畅的移动端3D体验。
