在这里插入图片描述

每日一句正能量

“没有哪一段经历是白费的,也没有哪一次跌倒是毫无意义的。”
看似走错的路、摔倒的跤,都在暗中塑造你的肌肉记忆和判断力。就像酿酒,坏葡萄也可能变成醋——但醋也有醋的用处。只要活着,你走过的每一步都在构成你。

前言

摘要:2026年,AI NPC智能体正从"脚本驱动"向"自主意识"进化。HarmonyOS 6(API 23)引入的鸿蒙智能体框架(HMAF)与端侧AI推理能力,为游戏开发者提供了构建"有灵魂NPC"的全新可能。本文将实战开发一款名为"智灵幻境"的开放世界角色扮演游戏,展示如何利用HMAF构建具备自主目标、持久记忆和动态对话的AI NPC智能体,通过悬浮导航实现游戏内多角色快速切换,基于沉浸光感打造"情绪即光效"的NPC交互体验,以及结合分布式软总线实现跨设备NPC托管与协作。


一、前言:AI NPC智能体的范式跃迁

传统游戏中的NPC(非玩家角色)长期受限于脚本驱动的行为模式——固定的对话树、预设的行动路径、机械的状态切换。玩家与NPC的互动往往停留在"点击对话→选择选项→获得奖励"的线性流程中,缺乏真正的沉浸感和情感连接。

2026年,随着大语言模型和智能体技术的成熟,AI NPC正经历从L1(模型阶段)到L3(类人智能体阶段)的跃迁。根据行业分析,AI NPC智能体具备四大核心能力:自主行为(独立目标与决策)、动态对话(实时生成对话)、持久记忆(跨会话关系积累)和环境感知(响应游戏世界变化)。

HarmonyOS 6(API 23)的HMAF框架不仅支持LLM模式、工作流模式、A2A模式和OpenClaw模式四种智能体编排方式,更关键的是将端侧AI推理能力与游戏引擎深度集成。配合悬浮导航沉浸光感特性,游戏开发者可以构建"情绪即光效"的NPC交互系统——NPC的喜怒哀乐不再只是对话框里的文字,而是整个游戏世界的光效变化。

本文核心亮点

  • 情绪感知光效系统:NPC的情绪状态(平静/好奇/友好/愤怒/恐惧/兴奋)实时映射为环境光色与脉冲节奏
  • HMAF NPC智能体引擎:基于Agent Framework Kit构建具备自主目标、持久记忆和动态对话的AI NPC
  • 悬浮角色导航:底部悬浮页签实现多NPC快速切换与组队管理,支持PC端鼠标悬停预览
  • 分布式NPC托管:利用HarmonyOS分布式软总线,实现手机端控制+PC端渲染的跨设备游戏体验
  • 端侧AI推理:基于MindSpore Lite实现NPC对话的端侧实时生成,无需联网即可交互

在这里插入图片描述

二、核心特性解析与技术选型

2.1 沉浸光感在游戏NPC中的价值

HarmonyOS 6的systemMaterialEffect通过模拟物理光照模型,为UI组件带来细腻的光晕与反射效果。在AI NPC游戏场景中,这种材质效果能够:

  • 情绪可视化:NPC的情绪状态直接映射为环境光色,玩家无需查看UI即可感知NPC心情
  • 氛围营造:玻璃拟态的半透明层让背景光效柔和过渡,营造"与有灵魂的NPC对话"的沉浸感
  • 状态即时反馈:NPC运行时光效脉冲变化,对话中时呼吸光、战斗时脉冲光、死亡时渐隐光

2.2 悬浮导航的游戏适配

与传统应用不同,开放世界RPG游戏需要处理:

  • 多角色快速切换:玩家常在主角、队友NPC、召唤物间快速切换
  • 信息密度平衡:既要保证导航可见,又不能遮挡游戏画面
  • PC端操作优化:支持鼠标悬停预览NPC状态、右键打开交互菜单

HarmonyOS 6的悬浮页签支持**强(85%)、平衡(70%)、弱(55%)**三档透明度自定义,结合PC端的自由窗口能力,可以实现"需要时出现,专注时隐退"的智能导航体验。

2.3 技术架构选型

技术模块 选用方案 说明
智能体框架 HMAF (HarmonyOS Multi-Agent Framework) 系统级NPC智能体能力
端侧AI推理 MindSpore Lite NPC对话端侧实时生成
意图理解 Intents Kit 玩家语音/手势意图解析
分布式能力 Distributed Service Kit 跨设备NPC托管
渲染引擎 Canvas + 3D Scene 游戏场景渲染
状态管理 AppStorage 跨组件/跨窗口状态同步
光效系统 SystemMaterialEffect + 自定义动画 情绪光效实现

在这里插入图片描述

三、项目实战:"智灵幻境"架构设计

3.1 应用场景与功能规划

面向HarmonyOS PC/手机的开放世界RPG游戏,核心功能包括:

功能模块 技术实现 沉浸光感/HMAF应用
游戏主场景 Canvas + 3D Scene 环境光随NPC情绪变化
NPC智能体 HMAF LLM Mode 自主对话与决策
悬浮角色导航 HdsTabs + systemMaterialEffect 玻璃拟态角色切换页签
NPC情绪面板 Form + HdsInput 选中NPC主题色同步
对话系统 MindSpore Lite + HMAF 端侧实时对话生成
分布式托管 Distributed Service Kit 手机控制+PC渲染
战斗系统 HMAF Workflow Mode 战斗流程编排
任务系统 HMAF A2A Mode 多NPC协作任务

3.2 技术架构图

在这里插入图片描述

┌─────────────────────────────────────────────────────────────┐
│                    智灵幻境 - AI NPC 智能体游戏                  │
├─────────────────────────────────────────────────────────────┤
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐         │
│  │ 沉浸光感层   │  │  游戏场景层  │  │ 悬浮导航层   │         │
│  │ (Ambient)   │  │  (GameScene)│  │ (FloatNav)  │         │
│  │ 情绪环境光   │  │  NPC渲染    │  │ 角色切换     │         │
│  └─────────────┘  └─────────────┘  └─────────────┘         │
├─────────────────────────────────────────────────────────────┤
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐         │
│  │ HMAF NPC引擎 │  │ 端侧AI推理  │  │ 分布式托管   │         │
│  │ (LLM/Work)  │  │ (MindSpore) │  │ (Distributed)│         │
│  │ 智能体决策   │  │ 对话生成    │  │ 跨设备协同   │         │
│  └─────────────┘  └─────────────┘  └─────────────┘         │
├─────────────────────────────────────────────────────────────┤
│  玩家交互层 (语音/手势/触控) + Intents Kit 意图理解          │
└─────────────────────────────────────────────────────────────┘

四、环境配置与模块依赖

4.1 模块依赖配置

// entry/oh-package.json5
{
  "dependencies": {
    "@kit.UIDesignKit": "^1.0.0",
    "@kit.ArkUI": "^1.0.0",
    "@kit.AgentFrameworkKit": "^1.0.0",
    "@kit.IntentsKit": "^1.0.0",
    "@kit.DistributedServiceKit": "^1.0.0",
    "@kit.MindSporeLiteKit": "^1.0.0"
  }
}

4.2 权限声明(module.json5)

{
  "module": {
    "name": "entry",
    "type": "entry",
    "description": "智灵幻境 - AI NPC智能体游戏",
    "mainElement": "GameAbility",
    "abilities": [
      {
        "name": "GameAbility",
        "srcEntry": "./ets/gameability/GameAbility.ets",
        "description": "游戏主窗口",
        "icon": "$media:icon",
        "label": "$string:GameAbility_label",
        "startWindowIcon": "$media:icon",
        "startWindowBackground": "$color:start_window_background",
        "windowMode": "fullscreen",
        "supportWindowMode": ["fullscreen", "split", "float"]
      }
    ],
    "requestPermissions": [
      {
        "name": "ohos.permission.INTERNET"
      },
      {
        "name": "ohos.permission.DISTRIBUTED_DATASYNC"
      },
      {
        "name": "ohos.permission.MICROPHONE"
      }
    ]
  }
}

五、核心组件实战

5.1 窗口沉浸配置(GameAbility.ets)

// entry/src/main/ets/gameability/GameAbility.ets
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';

export default class GameAbility extends UIAbility {
  onWindowStageCreate(windowStage: window.WindowStage): void {
    windowStage.loadContent('pages/GamePage', (err) => {
      if (err.code) {
        console.error('Failed to load content:', JSON.stringify(err));
        return;
      }
      console.info('Succeeded in loading content.');
      
      let mainWindow = windowStage.getMainWindowSync();
      this.configureImmersiveGameWindow(mainWindow);
    });
  }

  private configureImmersiveGameWindow(mainWindow: window.Window): void {
    // 设置窗口全屏布局,移除系统标题栏
    mainWindow.setWindowLayoutFullScreen(true);
    
    // 设置窗口背景为透明,让沉浸光效穿透
    mainWindow.setWindowBackgroundColor('#00000000');
    
    // 设置窗口亮度跟随内容
    mainWindow.setWindowKeepScreenOn(true);
    
    // 监听窗口焦点变化
    mainWindow.on('windowFocusChange', (isFocused: boolean) => {
      AppStorage.setOrCreate('game_focused', isFocused);
    });
    
    // 初始化分布式能力
    this.initDistributedService();
  }

  private async initDistributedService(): Promise<void> {
    // 注册分布式设备发现
    let distributedManager = distributedService.createManager();
    await distributedManager.init({
      deviceType: ['phone', 'tablet', 'pc'],
      serviceName: 'zhiling_game_service'
    });
    
    // 监听设备上线
    distributedManager.on('deviceOnline', (deviceInfo) => {
      console.info(`Device online: ${deviceInfo.deviceName}`);
      AppStorage.setOrCreate('distributed_device', deviceInfo);
    });
  }
}

代码亮点

  • setWindowLayoutFullScreen(true):移除系统标题栏,实现真正的无边框游戏沉浸体验
  • setWindowBackgroundColor('#00000000'):透明背景让底层光效层完全可见
  • 分布式设备发现:自动发现同账号下的手机、平板、PC设备,为跨设备NPC托管做准备

5.2 情绪感知光效系统(EmotionLightSystem.ets)

// entry/src/main/ets/systems/EmotionLightSystem.ets

export enum NPCState {
  IDLE = 'idle',
  PATROL = 'patrol',
  INTERACT = 'interact',
  COMBAT = 'combat',
  FLEE = 'flee',
  TRADE = 'trade'
}

export enum Emotion {
  CALM = 'calm',
  CURIOUS = 'curious',
  FRIENDLY = 'friendly',
  ANGRY = 'angry',
  FEARFUL = 'fearful',
  EXCITED = 'excited'
}

export interface LightEffectConfig {
  color: string;
  intensity: number;
  pulseSpeed: number;
  blurRadius: number;
  ambientColor: string;
}

@Component
export struct EmotionLightSystem {
  @State currentEmotion: Emotion = Emotion.CALM;
  @State lightIntensity: number = 0.6;
  @State pulsePhase: number = 0;

  // 情绪-光效映射表
  private emotionLightMap: Map<Emotion, LightEffectConfig> = new Map([
    [Emotion.CALM, {
      color: '#4CC9F0',
      intensity: 0.4,
      pulseSpeed: 3000,
      blurRadius: 100,
      ambientColor: 'rgba(76, 201, 240, 0.1)'
    }],
    [Emotion.CURIOUS, {
      color: '#00C9A7',
      intensity: 0.5,
      pulseSpeed: 2500,
      blurRadius: 120,
      ambientColor: 'rgba(0, 201, 167, 0.15)'
    }],
    [Emotion.FRIENDLY, {
      color: '#FF9F1C',
      intensity: 0.6,
      pulseSpeed: 2000,
      blurRadius: 150,
      ambientColor: 'rgba(255, 159, 28, 0.2)'
    }],
    [Emotion.ANGRY, {
      color: '#FF1744',
      intensity: 0.9,
      pulseSpeed: 800,
      blurRadius: 200,
      ambientColor: 'rgba(255, 23, 68, 0.3)'
    }],
    [Emotion.FEARFUL, {
      color: '#7B61FF',
      intensity: 0.7,
      pulseSpeed: 1500,
      blurRadius: 180,
      ambientColor: 'rgba(123, 97, 255, 0.25)'
    }],
    [Emotion.EXCITED, {
      color: '#FFD700',
      intensity: 0.8,
      pulseSpeed: 1000,
      blurRadius: 160,
      ambientColor: 'rgba(255, 215, 0, 0.25)'
    }]
  ]);

  aboutToAppear(): void {
    // 监听NPC情绪变化
    AppStorage.watch('npc_emotion', (emotion: Emotion) => {
      this.currentEmotion = emotion;
      this.triggerEmotionTransition(emotion);
    });
  }

  private triggerEmotionTransition(emotion: Emotion): void {
    const config = this.emotionLightMap.get(emotion);
    if (!config) return;

    // 触发全局光效变化
    AppStorage.setOrCreate('ambient_color', config.ambientColor);
    AppStorage.setOrCreate('ambient_intensity', config.intensity);
    
    // 触发NPC光效变化
    AppStorage.setOrCreate('npc_glow_color', config.color);
    AppStorage.setOrCreate('npc_glow_intensity', config.intensity);
    AppStorage.setOrCreate('npc_pulse_speed', config.pulseSpeed);
  }

  build() {
    Stack() {
      // 底层环境光
      this.buildAmbientLight()

      // 中层NPC光晕
      this.buildNPCGlow()

      // 顶层脉冲光效
      this.buildPulseEffect()
    }
    .width('100%')
    .height('100%')
    .pointerEvents(PointerEvents.None)
  }

  @Builder
  buildAmbientLight(): void {
    Column()
      .width('100%')
      .height('100%')
      .backgroundColor(this.emotionLightMap.get(this.currentEmotion)?.ambientColor || 'transparent')
      .animation({
        duration: 1000,
        curve: Curve.EaseInOut
      })
  }

  @Builder
  buildNPCGlow(): void {
    Column()
      .width('100%')
      .height('100%')
      .backgroundColor(this.emotionLightMap.get(this.currentEmotion)?.color || '#FFFFFF')
      .blur(this.emotionLightMap.get(this.currentEmotion)?.blurRadius || 100)
      .opacity(this.lightIntensity)
      .animation({
        duration: 800,
        curve: Curve.EaseInOut
      })
  }

  @Builder
  buildPulseEffect(): void {
    Column()
      .width('100%')
      .height('100%')
      .backgroundColor(this.emotionLightMap.get(this.currentEmotion)?.color || '#FFFFFF')
      .opacity(this.lightIntensity * 0.3)
      .animation({
        duration: this.emotionLightMap.get(this.currentEmotion)?.pulseSpeed || 2000,
        curve: Curve.EaseInOut,
        iterations: -1,
        playMode: PlayMode.Alternate
      })
      .scale({ x: 1.2, y: 1.2 })
  }
}

代码亮点

  • 情绪-光效映射表:6种情绪对应6套完整的光效配置(颜色、强度、脉冲速度、模糊半径、环境色)
  • 三层光效架构:环境光层 → NPC光晕层 → 脉冲光效层,层次分明
  • 平滑过渡动画:情绪切换时1000ms的缓动动画,避免光效突变造成视觉割裂
  • 无限脉冲循环:根据情绪类型设置不同的脉冲速度(愤怒800ms快速脉冲、平静3000ms缓慢呼吸)

5.3 AI NPC智能体引擎(AINPCEngine.ets)

// entry/src/main/ets/engine/AINPCEngine.ets
import { hmaf } from '@kit.AgentFrameworkKit';
import { mindSporeLite } from '@kit.MindSporeLiteKit';

export interface NPCProfile {
  id: string;
  name: string;
  role: string;
  personality: string;
  backstory: string;
  skills: string[];
  relationships: Map<string, string>;
  memory: NPCMemory[];
}

export interface NPCMemory {
  timestamp: number;
  event: string;
  emotion: string;
  importance: number;
}

export class AINPCEngine {
  private session: hmaf.AgentSession | null = null;
  private model: mindSporeLite.Model | null = null;
  private npcProfiles: Map<string, NPCProfile> = new Map();
  private longTermMemory: Map<string, NPCMemory[]> = new Map();

  async initialize(): Promise<void> {
    // 创建HMAF智能体会话
    this.session = await hmaf.createAgentSession({
      mode: hmaf.AgentMode.LLM,
      enableDistributed: true,
      maxConcurrentAgents: 20
    });

    // 加载端侧对话模型
    this.model = await mindSporeLite.loadModel({
      modelPath: 'models/npc_dialogue.mindir',
      context: {
        target: 'cpu',
        precisionMode: 'fp16'
      }
    });

    // 初始化NPC档案
    await this.initializeNPCProfiles();
  }

  private async initializeNPCProfiles(): Promise<void> {
    // 商人NPC
    this.npcProfiles.set('merchant_01', {
      id: 'merchant_01',
      name: '老周',
      role: '杂货商人',
      personality: '精明但热心,喜欢讨价还价,对稀有物品有独特眼光',
      backstory: '曾经在王城做过宫廷商人,因得罪权贵流落至此,但积累了丰富的人脉',
      skills: ['鉴定', '交易', '情报'],
      relationships: new Map([['player', '潜在客户']]),
      memory: []
    });

    // 战士NPC
    this.npcProfiles.set('warrior_01', {
      id: 'warrior_01',
      name: '铁山',
      role: '退役老兵',
      personality: '豪爽直率,重情重义,对弱者有保护欲',
      backstory: '曾是王国禁卫军统领,因一场战役失去左眼,现在以教导年轻人战斗技巧为生',
      skills: ['剑术', '战术', '领导力'],
      relationships: new Map([['player', '值得培养的年轻人']]),
      memory: []
    });

    // 医者NPC
    this.npcProfiles.set('healer_01', {
      id: 'healer_01',
      name: '青萝',
      role: '草药师',
      personality: '温柔内敛,对自然充满敬畏,说话轻声细语',
      backstory: '自幼跟随祖母学习草药知识,能听懂植物的语言,治愈过无数伤者',
      skills: ['治疗', '草药', '自然感知'],
      relationships: new Map([['player', '需要照顾的人']]),
      memory: []
    });
  }

  // 生成NPC对话
  async generateDialogue(
    npcId: string, 
    playerInput: string, 
    context: GameContext
  ): Promise<string> {
    const profile = this.npcProfiles.get(npcId);
    if (!profile) return '...';

    // 构建对话提示
    const prompt = this.buildDialoguePrompt(profile, playerInput, context);
    
    // 端侧推理生成回复
    const response = await this.model?.infer({
      input: prompt,
      maxLength: 200,
      temperature: 0.8
    });

    // 更新NPC记忆
    this.updateNPCMemory(npcId, playerInput, response?.text || '...');

    // 分析情绪并触发光效
    const emotion = await this.analyzeEmotion(response?.text || '');
    AppStorage.setOrCreate('npc_emotion', emotion);

    return response?.text || '...';
  }

  private buildDialoguePrompt(
    profile: NPCProfile, 
    playerInput: string, 
    context: GameContext
  ): string {
    const memoryContext = this.getRelevantMemory(profile.id);
    
    return `你是${profile.name},一个${profile.role}。
性格:${profile.personality}
背景:${profile.backstory}
技能:${profile.skills.join('、')}

相关记忆:
${memoryContext}

当前场景:${context.scene}
玩家说:${playerInput}

请以${profile.name}的身份和语气回复,保持角色一致性,回复控制在100字以内。`;
  }

  private getRelevantMemory(npcId: string): string {
    const memories = this.longTermMemory.get(npcId) || [];
    // 按重要性排序,取最近5条
    return memories
      .sort((a, b) => b.importance - a.importance)
      .slice(0, 5)
      .map(m => `- ${m.event} (${m.emotion})`)
      .join('\n');
  }

  private updateNPCMemory(npcId: string, playerInput: string, response: string): void {
    const memories = this.longTermMemory.get(npcId) || [];
    memories.push({
      timestamp: Date.now(),
      event: `玩家说:${playerInput},你回复:${response}`,
      emotion: 'neutral',
      importance: this.calculateImportance(playerInput)
    });
    
    // 限制记忆数量
    if (memories.length > 100) {
      memories.shift();
    }
    
    this.longTermMemory.set(npcId, memories);
  }

  private calculateImportance(input: string): number {
    // 简单的重要性计算:关键词匹配
    const importantKeywords = ['任务', '秘密', '宝藏', '危险', '承诺'];
    let score = 1;
    importantKeywords.forEach(keyword => {
      if (input.includes(keyword)) score += 2;
    });
    return Math.min(score, 10);
  }

  private async analyzeEmotion(text: string): Promise<Emotion> {
    // 情绪关键词匹配
    if (text.includes('愤怒') || text.includes('可恶') || text.includes('杀')) {
      return Emotion.ANGRY;
    }
    if (text.includes('害怕') || text.includes('危险') || text.includes('逃')) {
      return Emotion.FEARFUL;
    }
    if (text.includes('高兴') || text.includes('太好了') || text.includes('奖励')) {
      return Emotion.EXCITED;
    }
    if (text.includes('朋友') || text.includes('帮助') || text.includes('谢谢')) {
      return Emotion.FRIENDLY;
    }
    if (text.includes('奇怪') || text.includes('好奇') || text.includes('什么')) {
      return Emotion.CURIOUS;
    }
    return Emotion.CALM;
  }
}

代码亮点

  • NPC档案系统:每个NPC拥有完整的角色设定(性格、背景、技能、关系网),确保对话一致性
  • 长期记忆系统:NPC能够记住与玩家的历史互动,按重要性排序存储最近100条记忆
  • 端侧AI推理:基于MindSpore Lite实现对话的端侧实时生成,无需联网即可交互,保护玩家隐私
  • 情绪分析引擎:自动分析NPC回复中的情绪关键词,实时触发对应的光效变化

5.4 悬浮角色导航(CharacterFloatNav.ets)

// entry/src/main/ets/components/CharacterFloatNav.ets
import { HdsTabs, HdsTabItem } from '@kit.UIDesignKit';

export interface CharacterInfo {
  id: string;
  name: string;
  avatar: Resource;
  hp: number;
  maxHp: number;
  mp: number;
  maxMp: number;
  emotion: Emotion;
  isActive: boolean;
}

@Component
export struct CharacterFloatNav {
  @State characters: CharacterInfo[] = [];
  @State selectedIndex: number = 0;
  @State transparency: number = 0.70;
  @State isExpanded: boolean = false;
  @State avoidHeight: number = 0;

  // 情绪光色映射
  private emotionColors: Map<Emotion, string> = new Map([
    [Emotion.CALM, '#4CC9F0'],
    [Emotion.CURIOUS, '#00C9A7'],
    [Emotion.FRIENDLY, '#FF9F1C'],
    [Emotion.ANGRY, '#FF1744'],
    [Emotion.FEARFUL, '#7B61FF'],
    [Emotion.EXCITED, '#FFD700']
  ]);

  aboutToAppear(): void {
    // 初始化角色列表
    this.characters = [
      {
        id: 'player',
        name: '主角',
        avatar: $r('app.media.avatar_player'),
        hp: 100,
        maxHp: 100,
        mp: 80,
        maxMp: 100,
        emotion: Emotion.CALM,
        isActive: true
      },
      {
        id: 'merchant_01',
        name: '老周',
        avatar: $r('app.media.avatar_merchant'),
        hp: 60,
        maxHp: 60,
        mp: 40,
        maxMp: 50,
        emotion: Emotion.FRIENDLY,
        isActive: false
      },
      {
        id: 'warrior_01',
        name: '铁山',
        avatar: $r('app.media.avatar_warrior'),
        hp: 150,
        maxHp: 150,
        mp: 30,
        maxMp: 50,
        emotion: Emotion.CALM,
        isActive: false
      },
      {
        id: 'healer_01',
        name: '青萝',
        avatar: $r('app.media.avatar_healer'),
        hp: 80,
        maxHp: 80,
        mp: 120,
        maxMp: 120,
        emotion: Emotion.CURIOUS,
        isActive: false
      }
    ];

    // 获取底部安全区高度
    let windowInstance = window.getLastWindow(getContext(this));
    windowInstance.then((win) => {
      let avoidArea = win.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR);
      this.avoidHeight = avoidArea.bottomRect.height;
    });

    // 监听NPC情绪变化
    AppStorage.watch('npc_emotion', (emotion: Emotion) => {
      if (this.selectedIndex > 0) {
        this.characters[this.selectedIndex].emotion = emotion;
      }
    });
  }

  build() {
    Stack({ alignContent: Alignment.Bottom }) {
      Column() {
        // 角色信息展开面板(悬停时显示)
        if (this.isExpanded && this.selectedIndex >= 0) {
          this.buildCharacterDetailPanel()
        }

        // 悬浮角色页签
        HdsTabs({
          items: this.characters.map((char, index) => ({
            icon: char.avatar,
            label: char.name,
            badge: char.isActive ? 0 : undefined,
            customStyle: {
              backgroundColor: this.selectedIndex === index 
                ? this.emotionColors.get(char.emotion) 
                : 'transparent',
              borderColor: this.selectedIndex === index 
                ? this.emotionColors.get(char.emotion) 
                : 'rgba(255,255,255,0.2)'
            }
          })),
          selectedIndex: this.selectedIndex,
          onSelect: (index: number) => {
            this.selectedIndex = index;
            AppStorage.setOrCreate('selected_character', this.characters[index]);
            // 触发选中角色的情绪光效
            AppStorage.setOrCreate('npc_emotion', this.characters[index].emotion);
          },
          onHover: (index: number) => {
            this.isExpanded = true;
            // PC端悬停预览
            if (index >= 0) {
              AppStorage.setOrCreate('hover_character', this.characters[index]);
            }
          },
          backgroundStyle: {
            blurStyle: BlurStyle.REGULAR,
            backgroundColor: `rgba(20, 20, 30, ${this.transparency})`,
            borderRadius: 24
          },
          indicatorStyle: {
            color: this.emotionColors.get(this.characters[this.selectedIndex]?.emotion) || '#7B61FF',
            height: 3,
            width: 24,
            borderRadius: 2
          }
        })
        .height(64)
        .width('96%')
        .margin({ bottom: 12 })
      }
      .width('100%')
      .padding({ bottom: this.avoidHeight + 12 })
    }
    .width('100%')
    .height('100%')
    .pointerEvents(PointerEvents.BoxNone)
  }

  @Builder
  buildCharacterDetailPanel(): void {
    Column({ space: 8 }) {
      // 角色头像与名称
      Row({ space: 12 }) {
        Image(this.characters[this.selectedIndex].avatar)
          .width(48)
          .height(48)
          .borderRadius(24)
          .border({
            width: 2,
            color: this.emotionColors.get(this.characters[this.selectedIndex].emotion) || '#FFFFFF'
          })
          .shadow({
            radius: 10,
            color: this.emotionColors.get(this.characters[this.selectedIndex].emotion) || '#FFFFFF'
          })

        Column({ space: 4 }) {
          Text(this.characters[this.selectedIndex].name)
            .fontSize(16)
            .fontWeight(FontWeight.Bold)
            .fontColor('#FFFFFF')
          
          Text(this.getEmotionLabel(this.characters[this.selectedIndex].emotion))
            .fontSize(12)
            .fontColor(this.emotionColors.get(this.characters[this.selectedIndex].emotion) || '#FFFFFF')
        }
        .alignItems(HorizontalAlign.Start)
      }
      .width('100%')
      .padding(12)

      // 血条与蓝条
      Column({ space: 6 }) {
        // HP条
        Row({ space: 8 }) {
          Text('HP').fontSize(10).fontColor('#FF1744').width(30)
          Stack({ alignContent: Alignment.Start }) {
            Row()
              .width('100%')
              .height(8)
              .backgroundColor('rgba(255,23,68,0.2)')
              .borderRadius(4)
            Row()
              .width(`${(this.characters[this.selectedIndex].hp / this.characters[this.selectedIndex].maxHp) * 100}%`)
              .height(8)
              .backgroundColor('#FF1744')
              .borderRadius(4)
              .animation({ duration: 500, curve: Curve.EaseInOut })
          }
          .layoutWeight(1)
          Text(`${this.characters[this.selectedIndex].hp}/${this.characters[this.selectedIndex].maxHp}`)
            .fontSize(10)
            .fontColor('rgba(255,255,255,0.6)')
            .width(50)
        }
        .width('100%')

        // MP条
        Row({ space: 8 }) {
          Text('MP').fontSize(10).fontColor('#4CC9F0').width(30)
          Stack({ alignContent: Alignment.Start }) {
            Row()
              .width('100%')
              .height(8)
              .backgroundColor('rgba(76,201,240,0.2)')
              .borderRadius(4)
            Row()
              .width(`${(this.characters[this.selectedIndex].mp / this.characters[this.selectedIndex].maxMp) * 100}%`)
              .height(8)
              .backgroundColor('#4CC9F0')
              .borderRadius(4)
              .animation({ duration: 500, curve: Curve.EaseInOut })
          }
          .layoutWeight(1)
          Text(`${this.characters[this.selectedIndex].mp}/${this.characters[this.selectedIndex].maxMp}`)
            .fontSize(10)
            .fontColor('rgba(255,255,255,0.6)')
            .width(50)
        }
        .width('100%')
      }
      .width('100%')
      .padding({ left: 12, right: 12 })
    }
    .width('96%')
    .backgroundColor('rgba(20, 20, 30, 0.9)')
    .borderRadius(16)
    .border({
      width: 1,
      color: this.emotionColors.get(this.characters[this.selectedIndex].emotion) || 'rgba(255,255,255,0.1)'
    })
    .margin({ bottom: 8 })
    .animation({
      duration: 200,
      curve: Curve.EaseInOut
    })
  }

  private getEmotionLabel(emotion: Emotion): string {
    const labels: Map<Emotion, string> = new Map([
      [Emotion.CALM, '平静'],
      [Emotion.CURIOUS, '好奇'],
      [Emotion.FRIENDLY, '友好'],
      [Emotion.ANGRY, '愤怒'],
      [Emotion.FEARFUL, '恐惧'],
      [Emotion.EXCITED, '兴奋']
    ]);
    return labels.get(emotion) || '未知';
  }
}

代码亮点

  • 情绪感知角色页签:每个角色的页签边框和指示器颜色随其当前情绪动态变化
  • 悬停详情面板:PC端鼠标悬停时展开角色详细信息(HP/MP/情绪状态),支持实时数据更新
  • 玻璃拟态导航BlurStyle.REGULAR配合动态透明度,实现悬浮于游戏画面之上的玻璃质感
  • 点击穿透pointerEvents(BoxNone)确保导航不遮挡游戏画面操作

5.5 NPC对话界面(NPCDialoguePage.ets)

// entry/src/main/ets/pages/NPCDialoguePage.ets
import { AINPCEngine } from '../engine/AINPCEngine';

@Component
struct NPCDialoguePage {
  @State npcId: string = '';
  @State npcName: string = '';
  @State npcAvatar: Resource = $r('app.media.avatar_default');
  @State dialogueHistory: DialogueMessage[] = [];
  @State currentInput: string = '';
  @State isTyping: boolean = false;
  @State emotion: Emotion = Emotion.CALM;

  private engine: AINPCEngine = new AINPCEngine();
  private emotionColors: Map<Emotion, string> = new Map([
    [Emotion.CALM, '#4CC9F0'],
    [Emotion.CURIOUS, '#00C9A7'],
    [Emotion.FRIENDLY, '#FF9F1C'],
    [Emotion.ANGRY, '#FF1744'],
    [Emotion.FEARFUL, '#7B61FF'],
    [Emotion.EXCITED, '#FFD700']
  ]);

  aboutToAppear(): void {
    // 获取选中的NPC
    let selectedNPC = AppStorage.get('selected_character') as CharacterInfo;
    if (selectedNPC) {
      this.npcId = selectedNPC.id;
      this.npcName = selectedNPC.name;
      this.npcAvatar = selectedNPC.avatar;
      this.emotion = selectedNPC.emotion;
    }

    // 初始化引擎
    this.engine.initialize();
  }

  build() {
    Stack() {
      // 背景光效层
      Column()
        .width('100%')
        .height('100%')
        .backgroundColor(this.emotionColors.get(this.emotion) || '#4CC9F0')
        .blur(200)
        .opacity(0.15)
        .animation({
          duration: 1000,
          curve: Curve.EaseInOut
        })

      // 对话内容层
      Column() {
        // NPC信息栏
        this.buildNPCHeader()

        // 对话历史
        List({ space: 12 }) {
          ForEach(this.dialogueHistory, (msg: DialogueMessage, index: number) => {
            ListItem() {
              this.buildMessageBubble(msg)
            }
          }, (msg: DialogueMessage, index: number) => `${msg.timestamp}-${index}`)
        }
        .width('100%')
        .layoutWeight(1)
        .padding(16)
        .edgeEffect(EdgeEffect.Spring)

        // 输入区域
        this.buildInputArea()
      }
      .width('100%')
      .height('100%')
    }
    .width('100%')
    .height('100%')
    .backgroundColor('rgba(10, 10, 15, 0.95)')
  }

  @Builder
  buildNPCHeader(): void {
    Row({ space: 16 }) {
      // NPC头像
      Stack() {
        Image(this.npcAvatar)
          .width(56)
          .height(56)
          .borderRadius(28)
          .border({
            width: 3,
            color: this.emotionColors.get(this.emotion) || '#FFFFFF'
          })
        
        // 情绪指示点
        Circle()
          .width(12)
          .height(12)
          .fill(this.emotionColors.get(this.emotion) || '#FFFFFF')
          .position({ x: 40, y: 40 })
          .shadow({
            radius: 6,
            color: this.emotionColors.get(this.emotion) || '#FFFFFF'
          })
          .animation({
            duration: 1000,
            curve: Curve.EaseInOut,
            iterations: -1,
            playMode: PlayMode.Alternate
          })
          .scale({ x: 1.3, y: 1.3 })
      }

      Column({ space: 4 }) {
        Text(this.npcName)
          .fontSize(18)
          .fontWeight(FontWeight.Bold)
          .fontColor('#FFFFFF')
        
        Text(this.getEmotionLabel(this.emotion))
          .fontSize(12)
          .fontColor(this.emotionColors.get(this.emotion) || '#FFFFFF')
      }
      .alignItems(HorizontalAlign.Start)

      Blank()

      // 关闭按钮
      Button({ type: ButtonType.Circle }) {
        Image($r('app.media.ic_close'))
          .width(20)
          .height(20)
          .fillColor('rgba(255,255,255,0.6)')
      }
      .width(36)
      .height(36)
      .backgroundColor('rgba(255,255,255,0.1)')
      .onClick(() => {
        // 返回游戏场景
        AppStorage.setOrCreate('game_state', 'exploring');
      })
    }
    .width('100%')
    .height(80)
    .padding({ left: 16, right: 16 })
    .backgroundColor('rgba(255,255,255,0.03)')
    .border({ width: { bottom: 1 }, color: 'rgba(255,255,255,0.1)' })
  }

  @Builder
  buildMessageBubble(msg: DialogueMessage): void {
    Row() {
      if (msg.sender === 'player') {
        Blank()
      }

      Column({ space: 4 }) {
        Text(msg.content)
          .fontSize(14)
          .fontColor(msg.sender === 'player' ? '#FFFFFF' : 'rgba(255,255,255,0.9)')
          .maxWidth('70%')
          .padding(12)
          .backgroundColor(msg.sender === 'player' 
            ? 'rgba(123, 97, 255, 0.3)' 
            : 'rgba(255,255,255,0.08)')
          .borderRadius(16)
          .border({
            width: 1,
            color: msg.sender === 'player' 
              ? 'rgba(123, 97, 255, 0.5)' 
              : 'rgba(255,255,255,0.1)'
          })

        Text(this.formatTime(msg.timestamp))
          .fontSize(10)
          .fontColor('rgba(255,255,255,0.4)')
      }
      .alignItems(msg.sender === 'player' ? HorizontalAlign.End : HorizontalAlign.Start)

      if (msg.sender === 'npc') {
        Blank()
      }
    }
    .width('100%')
  }

  @Builder
  buildInputArea(): void {
    Row({ space: 12 }) {
      // 语音输入按钮
      Button({ type: ButtonType.Circle }) {
        Image($r('app.media.ic_mic'))
          .width(20)
          .height(20)
          .fillColor('#FFFFFF')
      }
      .width(40)
      .height(40)
      .backgroundColor('rgba(255,255,255,0.1)')
      .onClick(() => {
        // 触发语音输入
        AppStorage.setOrCreate('input_mode', 'voice');
      })

      // 文本输入框
      TextInput({ placeholder: '与NPC对话...', text: $$this.currentInput })
        .width('70%')
        .height(40)
        .backgroundColor('rgba(255,255,255,0.08)')
        .fontColor('#FFFFFF')
        .placeholderColor('rgba(255,255,255,0.4)')
        .borderRadius(20)
        .onSubmit(() => {
          this.sendMessage();
        })

      // 发送按钮
      Button({ type: ButtonType.Circle }) {
        Image($r('app.media.ic_send'))
          .width(20)
          .height(20)
          .fillColor('#FFFFFF')
      }
      .width(40)
      .height(40)
      .backgroundColor(this.emotionColors.get(this.emotion) || '#7B61FF')
      .onClick(() => {
        this.sendMessage();
      })
    }
    .width('100%')
    .height(64)
    .padding({ left: 16, right: 16 })
    .backgroundColor('rgba(255,255,255,0.03)')
    .border({ width: { top: 1 }, color: 'rgba(255,255,255,0.1)' })
  }

  private async sendMessage(): Promise<void> {
    if (!this.currentInput.trim()) return;

    // 添加玩家消息
    this.dialogueHistory.push({
      sender: 'player',
      content: this.currentInput,
      timestamp: Date.now()
    });

    const input = this.currentInput;
    this.currentInput = '';
    this.isTyping = true;

    // 生成NPC回复
    const response = await this.engine.generateDialogue(this.npcId, input, {
      scene: '村庄广场',
      time: '傍晚',
      weather: '晴朗'
    });

    // 添加NPC回复
    this.dialogueHistory.push({
      sender: 'npc',
      content: response,
      timestamp: Date.now()
    });

    this.isTyping = false;
  }

  private formatTime(timestamp: number): string {
    const date = new Date(timestamp);
    return `${date.getHours()}:${date.getMinutes().toString().padStart(2, '0')}`;
  }

  private getEmotionLabel(emotion: Emotion): string {
    const labels: Map<Emotion, string> = new Map([
      [Emotion.CALM, '平静'],
      [Emotion.CURIOUS, '好奇'],
      [Emotion.FRIENDLY, '友好'],
      [Emotion.ANGRY, '愤怒'],
      [Emotion.FEARFUL, '恐惧'],
      [Emotion.EXCITED, '兴奋']
    ]);
    return labels.get(emotion) || '未知';
  }
}

代码亮点

  • 情绪感知对话背景:整个对话界面的背景光色随NPC当前情绪动态变化
  • 情绪指示呼吸灯:NPC头像右下角的情绪指示点持续脉冲呼吸,颜色对应情绪
  • 玻璃拟态消息气泡:玩家消息紫色半透明、NPC消息白色半透明,视觉区分清晰
  • 语音输入支持:支持语音输入与NPC对话,结合Intents Kit实现语音意图理解

5.6 分布式NPC托管(DistributedNPCHost.ets)

// entry/src/main/ets/distributed/DistributedNPCHost.ets
import { distributedService } from '@kit.DistributedServiceKit';

export class DistributedNPCHost {
  private manager: distributedService.Manager | null = null;
  private remoteDevices: distributedService.DeviceInfo[] = [];
  private hostedNPCs: Map<string, string> = new Map(); // npcId -> deviceId

  async initialize(): Promise<void> {
    this.manager = distributedService.createManager();
    
    // 初始化分布式服务
    await this.manager.init({
      deviceType: ['phone', 'tablet', 'pc'],
      serviceName: 'zhiling_npc_host',
      abilityName: 'NPCDistributedAbility'
    });

    // 监听设备变化
    this.manager.on('deviceStateChange', (deviceInfo, state) => {
      if (state === 'online') {
        this.remoteDevices.push(deviceInfo);
        this.redistributeNPCs();
      } else if (state === 'offline') {
        this.remoteDevices = this.remoteDevices.filter(d => d.deviceId !== deviceInfo.deviceId);
        this.recoverHostedNPCs(deviceInfo.deviceId);
      }
    });
  }

  // 智能分配NPC到最优设备
  private async redistributeNPCs(): Promise<void> {
    const npcList = Array.from(this.hostedNPCs.keys());
    
    for (const npcId of npcList) {
      const bestDevice = this.selectBestDevice(npcId);
      if (bestDevice && this.hostedNPCs.get(npcId) !== bestDevice.deviceId) {
        await this.migrateNPC(npcId, bestDevice.deviceId);
      }
    }
  }

  private selectBestDevice(npcId: string): distributedService.DeviceInfo | null {
    // 根据NPC类型选择最优设备
    // PC端:适合渲染复杂NPC(战斗、动画)
    // 手机端:适合轻量NPC(对话、交易)
    // 平板端:适合中等复杂度NPC
    
    const npcType = this.getNPCType(npcId);
    
    if (npcType === 'combat' || npcType === 'boss') {
      // 优先分配到PC端
      return this.remoteDevices.find(d => d.deviceType === 'pc') || null;
    } else if (npcType === 'merchant' || npcType === 'healer') {
      // 优先分配到手机端
      return this.remoteDevices.find(d => d.deviceType === 'phone') || null;
    }
    
    // 默认分配到当前设备
    return null;
  }

  private async migrateNPC(npcId: string, targetDeviceId: string): Promise<void> {
    // 保存NPC状态
    const npcState = await this.saveNPCState(npcId);
    
    // 发送到目标设备
    await this.manager?.sendMessage({
      targetDeviceId,
      message: {
        type: 'npc_migrate',
        npcId,
        state: npcState
      }
    });

    // 更新托管映射
    this.hostedNPCs.set(npcId, targetDeviceId);
    
    console.info(`NPC ${npcId} migrated to device ${targetDeviceId}`);
  }

  private async saveNPCState(npcId: string): Promise<string> {
    // 序列化NPC当前状态
    const state = {
      npcId,
      position: AppStorage.get(`npc_${npcId}_position`),
      emotion: AppStorage.get(`npc_${npcId}_emotion`),
      memory: AppStorage.get(`npc_${npcId}_memory`),
      health: AppStorage.get(`npc_${npcId}_health`)
    };
    return JSON.stringify(state);
  }

  private async recoverHostedNPCs(deviceId: string): Promise<void> {
    // 设备离线时,回收托管的NPC
    const affectedNPCs = Array.from(this.hostedNPCs.entries())
      .filter(([_, devId]) => devId === deviceId)
      .map(([npcId, _]) => npcId);

    for (const npcId of affectedNPCs) {
      // 恢复到本地设备
      this.hostedNPCs.set(npcId, 'local');
      console.info(`NPC ${npcId} recovered to local device`);
    }
  }

  private getNPCType(npcId: string): string {
    if (npcId.includes('boss')) return 'boss';
    if (npcId.includes('warrior')) return 'combat';
    if (npcId.includes('merchant')) return 'merchant';
    if (npcId.includes('healer')) return 'healer';
    return 'generic';
  }
}

代码亮点

  • 智能设备选择:根据NPC类型(战斗/商人/医者)自动选择最优设备(PC/手机/平板)
  • NPC状态迁移:支持NPC状态的序列化、跨设备传输和反序列化
  • 故障自动恢复:设备离线时自动将NPC回收至本地设备,保证游戏连续性
  • 负载均衡:根据设备性能和NPC复杂度动态分配,优化整体游戏体验

5.7 主游戏页面集成(GamePage.ets)

// entry/src/main/ets/pages/GamePage.ets
import { EmotionLightSystem } from '../systems/EmotionLightSystem';
import { CharacterFloatNav } from '../components/CharacterFloatNav';
import { AINPCEngine } from '../engine/AINPCEngine';
import { DistributedNPCHost } from '../distributed/DistributedNPCHost';

@Entry
@Component
struct GamePage {
  @State gameState: string = 'exploring'; // exploring | dialogue | combat | trading
  @State currentEmotion: Emotion = Emotion.CALM;
  @State themeColor: string = '#4CC9F0';
  @State lightIntensity: number = 0.6;

  private npcEngine: AINPCEngine = new AINPCEngine();
  private distributedHost: DistributedNPCHost = new DistributedNPCHost();

  aboutToAppear(): void {
    // 初始化引擎
    this.npcEngine.initialize();
    this.distributedHost.initialize();

    // 监听游戏状态变化
    AppStorage.watch('game_state', (state: string) => {
      this.gameState = state;
    });

    // 监听NPC情绪变化
    AppStorage.watch('npc_emotion', (emotion: Emotion) => {
      this.currentEmotion = emotion;
      this.themeColor = this.getEmotionColor(emotion);
    });
  }

  private getEmotionColor(emotion: Emotion): string {
    const colors: Map<Emotion, string> = new Map([
      [Emotion.CALM, '#4CC9F0'],
      [Emotion.CURIOUS, '#00C9A7'],
      [Emotion.FRIENDLY, '#FF9F1C'],
      [Emotion.ANGRY, '#FF1744'],
      [Emotion.FEARFUL, '#7B61FF'],
      [Emotion.EXCITED, '#FFD700']
    ]);
    return colors.get(emotion) || '#4CC9F0';
  }

  build() {
    Stack() {
      // 第一层:情绪光效背景
      EmotionLightSystem()

      // 第二层:游戏场景层
      this.buildGameScene()

      // 第三层:UI层
      Column() {
        // 顶部状态栏
        this.buildStatusBar()

        // 中间内容区(根据游戏状态切换)
        if (this.gameState === 'dialogue') {
          NPCDialoguePage()
        } else if (this.gameState === 'combat') {
          CombatPage()
        } else if (this.gameState === 'trading') {
          TradingPage()
        }

        // 底部悬浮角色导航
        CharacterFloatNav()
      }
      .width('100%')
      .height('100%')
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#0a0a0f')
    .expandSafeArea(
      [SafeAreaType.SYSTEM],
      [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM, SafeAreaEdge.START, SafeAreaEdge.END]
    )
  }

  @Builder
  buildGameScene(): void {
    // 游戏场景渲染(Canvas/3D)
    Canvas(this.gameContext)
      .width('100%')
      .height('100%')
      .onReady((context) => {
        this.renderGameScene(context);
      })
      .onAreaChange((oldArea, newArea) => {
        // 响应区域变化
      })
  }

  @Builder
  buildStatusBar(): void {
    Row({ space: 16 }) {
      // 时间显示
      Text(this.getGameTime())
        .fontSize(14)
        .fontColor('rgba(255,255,255,0.8)')

      Blank()

      // 当前区域
      Text('青石村')
        .fontSize(14)
        .fontColor('rgba(255,255,255,0.8)')

      Blank()

      // 网络状态
      Row({ space: 4 }) {
        Circle()
          .width(8)
          .height(8)
          .fill('#00C853')
        Text('在线')
          .fontSize(12)
          .fontColor('rgba(255,255,255,0.6)')
      }
    }
    .width('100%')
    .height(40)
    .padding({ left: 16, right: 16 })
    .backgroundColor('rgba(0,0,0,0.3)')
    .border({ width: { bottom: 1 }, color: 'rgba(255,255,255,0.1)' })
  }

  private renderGameScene(context: CanvasRenderingContext2D): void {
    // 渲染游戏场景
    // 包括地形、建筑、NPC、特效等
    context.fillStyle = '#1a1a2e';
    context.fillRect(0, 0, 1920, 1080);
    
    // 渲染NPC
    this.renderNPCs(context);
    
    // 渲染光效
    this.renderLightEffects(context);
  }

  private renderNPCs(context: CanvasRenderingContext2D): void {
    // 获取所有NPC位置并渲染
    const npcPositions = AppStorage.get('npc_positions') as Map<string, Position>;
    if (!npcPositions) return;

    npcPositions.forEach((pos, npcId) => {
      // 渲染NPC精灵
      context.beginPath();
      context.arc(pos.x, pos.y, 20, 0, 2 * Math.PI);
      context.fillStyle = this.getEmotionColor(
        AppStorage.get(`npc_${npcId}_emotion`) as Emotion || Emotion.CALM
      );
      context.fill();
      
      // 渲染NPC名称
      context.fillStyle = '#FFFFFF';
      context.font = '12px sans-serif';
      context.fillText(npcId, pos.x - 20, pos.y - 25);
    });
  }

  private renderLightEffects(context: CanvasRenderingContext2D): void {
    // 渲染环境光效
    const gradient = context.createRadialGradient(
      960, 540, 0,
      960, 540, 500
    );
    gradient.addColorStop(0, this.themeColor + '20');
    gradient.addColorStop(1, 'transparent');
    
    context.fillStyle = gradient;
    context.fillRect(0, 0, 1920, 1080);
  }

  private getGameTime(): string {
    const now = new Date();
    return `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`;
  }
}

六、NPC状态机与情绪光效联动

在这里插入图片描述

6.1 状态机设计

// entry/src/main/ets/states/NPCStateMachine.ets

export class NPCStateMachine {
  private currentState: NPCState = NPCState.IDLE;
  private transitions: Map<NPCState, NPCState[]> = new Map([
    [NPCState.IDLE, [NPCState.PATROL, NPCState.INTERACT]],
    [NPCState.PATROL, [NPCState.IDLE, NPCState.INTERACT]],
    [NPCState.INTERACT, [NPCState.IDLE, NPCState.COMBAT, NPCState.TRADE]],
    [NPCState.COMBAT, [NPCState.IDLE, NPCState.FLEE]],
    [NPCState.FLEE, [NPCState.IDLE]],
    [NPCState.TRADE, [NPCState.IDLE]]
  ]);

  async transition(newState: NPCState, context: GameContext): Promise<boolean> {
    const validTransitions = this.transitions.get(this.currentState) || [];
    if (!validTransitions.includes(newState)) {
      console.warn(`Invalid transition: ${this.currentState} -> ${newState}`);
      return false;
    }

    // 执行状态退出逻辑
    await this.onExitState(this.currentState);

    // 更新状态
    this.currentState = newState;

    // 执行状态进入逻辑
    await this.onEnterState(newState, context);

    // 触发情绪光效变化
    const emotion = this.stateToEmotion(newState);
    AppStorage.setOrCreate('npc_emotion', emotion);

    return true;
  }

  private async onEnterState(state: NPCState, context: GameContext): Promise<void> {
    switch (state) {
      case NPCState.IDLE:
        // 播放待机动画
        AppStorage.setOrCreate('npc_animation', 'idle');
        break;
      case NPCState.PATROL:
        // 开始巡逻路径
        AppStorage.setOrCreate('npc_animation', 'walk');
        break;
      case NPCState.INTERACT:
        // 面向玩家
        AppStorage.setOrCreate('npc_animation', 'talk');
        break;
      case NPCState.COMBAT:
        // 进入战斗姿态
        AppStorage.setOrCreate('npc_animation', 'combat');
        break;
      case NPCState.FLEE:
        // 逃跑动画
        AppStorage.setOrCreate('npc_animation', 'run');
        break;
      case NPCState.TRADE:
        // 打开交易界面
        AppStorage.setOrCreate('game_state', 'trading');
        break;
    }
  }

  private async onExitState(state: NPCState): Promise<void> {
    // 清理状态相关资源
    console.info(`Exiting state: ${state}`);
  }

  private stateToEmotion(state: NPCState): Emotion {
    switch (state) {
      case NPCState.IDLE: return Emotion.CALM;
      case NPCState.PATROL: return Emotion.CURIOUS;
      case NPCState.INTERACT: return Emotion.FRIENDLY;
      case NPCState.COMBAT: return Emotion.ANGRY;
      case NPCState.FLEE: return Emotion.FEARFUL;
      case NPCState.TRADE: return Emotion.EXCITED;
      default: return Emotion.CALM;
    }
  }
}

七、关键技术总结

7.1 HMAF游戏智能体开发清单

技术点 API/方法 应用场景
智能体会话创建 hmaf.createAgentSession({ mode: LLM }) NPC自主对话
端侧模型加载 mindSporeLite.loadModel({ modelPath }) 实时对话生成
意图解析 intents.createIntentEngine({ supportedDomains }) 玩家语音/手势理解
状态机管理 NPCStateMachine NPC行为状态切换
分布式服务 distributedService.createManager() 跨设备NPC托管
情绪光效 SystemMaterialEffect.IMMERSIVE 情绪可视化

7.2 沉浸光感实现清单

技术点 API/方法 应用场景
情绪光效映射 emotionLightMap 6种情绪对应6套光效
三层光效架构 Ambient + Glow + Pulse 环境光+光晕+脉冲
平滑过渡 animation({ duration: 1000 }) 情绪切换动画
无限脉冲 iterations: -1 持续呼吸光效

7.3 悬浮导航游戏适配要点

  1. 角色快速切换:底部悬浮页签支持主角+队友NPC的快速切换
  2. 情绪可视化:每个角色的页签颜色随其情绪动态变化
  3. 悬停详情:PC端鼠标悬停展开角色详细信息(HP/MP/情绪)
  4. 点击穿透pointerEvents(BoxNone)确保不遮挡游戏操作

八、调试与性能优化

8.1 真机调试建议

  1. 光效真机验证:玻璃拟态效果在模拟器上可能显示异常,建议在支持HarmonyOS 6的真机上测试
  2. 端侧推理测试:验证MindSpore Lite模型在不同设备上的推理性能
  3. 分布式测试:验证多设备间的NPC状态同步与迁移

8.2 性能优化策略

  1. 光效降级:低端设备上降低模糊半径和动画帧率
  2. 模型量化:使用INT8量化减少端侧模型内存占用
  3. 记忆裁剪:NPC长期记忆超过100条时自动裁剪低重要性记忆
  4. 分布式负载均衡:根据设备性能动态调整NPC分配策略

九、总结与展望

本文基于HarmonyOS 6(API 23)的悬浮导航沉浸光感特性,完整实战了一款名为"智灵幻境"的AI NPC智能体游戏。核心创新点总结:

  1. 情绪感知光效系统:6种NPC情绪(平静/好奇/友好/愤怒/恐惧/兴奋)实时映射为环境光色与脉冲节奏,让NPC的"灵魂"可见可感

  2. HMAF NPC智能体引擎:基于Agent Framework Kit构建具备自主目标、持久记忆和动态对话的AI NPC,每个NPC拥有完整的角色设定和长期记忆

  3. 端侧AI推理:基于MindSpore Lite实现NPC对话的端侧实时生成,无需联网即可交互,保护玩家隐私

  4. 悬浮角色导航:底部悬浮页签实现多NPC快速切换与组队管理,支持PC端鼠标悬停预览

  5. 分布式NPC托管:利用HarmonyOS分布式软总线,实现手机端控制+PC端渲染的跨设备游戏体验

未来扩展方向

  • 接入多智能体协作:多个NPC智能体之间通过A2A模式自主协作,形成动态社交网络
  • 探索AI生成内容:基于NPC记忆和情绪状态,自动生成个性化任务和剧情
  • 结合AR能力:通过摄像头将AI NPC投射到现实世界,实现虚实融合的沉浸体验
  • 情感计算升级:通过玩家语音语调分析真实情绪,NPC做出更精准的情感回应

转载自:https://blog.csdn.net/u014727709/article/details/162389721
欢迎 👍点赞✍评论⭐收藏,欢迎指正

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐