HarmonyOS 6(API 23)游戏开发实战:基于悬浮导航与沉浸光感的“光影迷宫“解谜游戏

文章目录


每日一句正能量

人生是一条路,人生有多长,路就有多长。人生有多深,路就有多宽。我们往往无法选择人生的长度,但是我们可以选择人生的深度。我们无法预知道路的长度,但是我们可以加宽道路的宽度。

前言

摘要:HarmonyOS 6(API 23)的悬浮导航与沉浸光感不仅是系统UI的革新,更为游戏开发提供了全新的交互与视觉范式。本文将实战开发一款"光影迷宫"解谜游戏,展示悬浮导航如何作为游戏HUD(抬头显示)控件,沉浸光感如何营造动态光影氛围,以及两者如何协同打造"无边界"的沉浸式游戏体验。


一、游戏场景下的悬浮导航与沉浸光感:从UI到玩法

1.1 游戏化重新定位

在传统应用中,悬浮导航是内容切换的工具;而在游戏中,它可以演变为游戏HUD控件层------悬浮于游戏画面上方,提供关卡选择、道具栏、设置等快捷入口,同时不遮挡核心游戏区域 。

沉浸光感在游戏中的价值更为独特:

  • 动态氛围渲染:根据游戏场景(地牢/森林/冰原)实时切换环境光色
  • 交互反馈增强:玩家操作触发光效脉冲,强化打击感与成就感
  • 状态指示:角色血量、能量值通过光效明暗直观呈现

1.2 技术架构对比

维度 传统游戏UI HarmonyOS 6 游戏UI
导航栏 固定底部/侧边栏 悬浮玻璃卡片,可动态隐藏
光效 预烘焙贴图 实时光照模型 + 系统光效同步
沉浸度 窗口模式 全屏沉浸 + 安全区扩展
交互反馈 简单音效 光效脉冲 + 微震动 + 音效

二、项目实战:"光影迷宫"架构设计

2.1 游戏玩法与功能规划

"光影迷宫"是一款光线解谜游戏,核心机制:

  1. 光影解谜:玩家通过旋转镜面引导光线点亮目标,悬浮导航提供道具(棱镜/分光器/反射镜)
  2. 沉浸光感氛围:不同关卡主题色动态渲染全屏环境光,通关时触发全屏光爆特效
  3. 悬浮HUD控制台:底部悬浮操作面板,支持道具拖拽、关卡切换、设置调节
  4. 自适应材质:根据设备性能自动调整光效复杂度(高/中/低画质)

2.2 技术架构图

复制代码
┌─────────────────────────────────────────────────────────┐
│                    游戏渲染层 (Canvas/WebGL)               │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────┐   │
│  │ 光线追踪引擎 │  │  粒子系统    │  │  镜面物理模拟    │   │
│  │ (Canvas 2D) │  │ (光爆特效)   │  │  (反射/折射)    │   │
│  └──────┬──────┘  └──────┬──────┘  └─────────────────┘   │
└─────────┼────────────────┼─────────────────────────────────┘
          │                │
┌─────────▼────────────────▼─────────────────────────────┐
│              ArkUI 悬浮HUD与沉浸光感层                    │
│  ┌─────────────────┐      ┌─────────────────────────┐   │
│  │  悬浮道具栏      │      │    沉浸光效背景层        │   │
│  │  · 玻璃拟态卡片   │      │  · 动态环境光晕          │   │
│  │  · 拖拽交互      │      │  · 通关光爆动画          │   │
│  │  · 三档透明度    │      │  · 主题色同步            │   │
│  └─────────────────┘      └─────────────────────────┘   │
└─────────────────────────────────────────────────────────┘

三、环境配置与游戏引擎初始化

3.1 模块依赖配置

oh-package.json5 中添加游戏开发所需依赖:

json 复制代码
{
  "dependencies": {
    "@kit.ArkUI": "^6.1.0",
    "@kit.AbilityKit": "^6.1.0",
    "@kit.BasicServicesKit": "^6.1.0",
    "@kit.SensorServiceKit": "^6.1.0"
  }
}

3.2 游戏窗口沉浸配置(GameAbility.ets)

代码亮点:游戏窗口需要极致的沉浸体验,隐藏所有系统UI,启用全屏布局,并设置高帧率模式(90Hz/120Hz)确保流畅度。

typescript 复制代码
// entry/src/main/ets/entryability/GameAbility.ets
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';

export default class GameAbility extends UIAbility {
  private windowStage: window.WindowStage | null = null;

  onWindowStageCreate(windowStage: window.WindowStage): void {
    this.windowStage = windowStage;

    windowStage.loadContent('pages/GamePage', (err) => {
      if (err.code) {
        console.error('Failed to load game content:', JSON.stringify(err));
        return;
      }
      this.setupGameWindow(windowStage);
    });
  }

  /**
   * 配置游戏专用窗口:极致沉浸 + 高帧率
   * 关键设置:
   * 1. 全屏沉浸:内容延伸至所有边缘,隐藏系统栏
   * 2. 高帧率模式:游戏场景需要90Hz/120Hz流畅体验
   * 3. 防误触:游戏过程中禁用系统手势
   * 4. 透明背景:允许游戏光效穿透至窗口边缘
   */
  private async setupGameWindow(windowStage: window.WindowStage): Promise<void> {
    try {
      const mainWindow = windowStage.getMainWindowSync();

      // 1. 全屏沉浸布局
      await mainWindow.setWindowLayoutFullScreen(true);
      await mainWindow.setWindowBackgroundColor('#00000000');

      // 2. 隐藏系统栏,游戏内自定义HUD
      await mainWindow.setWindowSystemBarEnable(false);
      await mainWindow.setWindowSystemBarProperties({
        statusBarColor: '#00000000',
        navigationBarColor: '#00000000'
      });

      // 3. 启用高帧率(设备支持时)
      try {
        await mainWindow.setPreferredFrameRate(120); // 120Hz高刷
      } catch (e) {
        console.info('Device does not support 120Hz, fallback to 60Hz');
        await mainWindow.setPreferredFrameRate(60);
      }

      // 4. 游戏防误触:禁用边缘手势
      await mainWindow.setWindowGestureDisabled(true);

      // 5. 窗口阴影与圆角(PC端游戏窗口)
      await mainWindow.setWindowShadowEnabled(true);
      await mainWindow.setWindowCornerRadius(8);

      // 6. 安全区避让(保留悬浮HUD空间)
      await mainWindow.setWindowAvoidAreaOption({
        type: window.AvoidAreaType.TYPE_SYSTEM,
        enabled: true
      });

      // 保存窗口实例
      AppStorage.setOrCreate('game_window', mainWindow);

      console.info('Game window setup completed - 120Hz immersive mode');
    } catch (error) {
      console.error('Failed to setup game window:', (error as BusinessError).message);
    }
  }

  onWindowStageDestroy(): void {
    this.windowStage = null;
  }
}

四、核心游戏组件实战

4.1 游戏主画布:光线追踪与镜面物理

代码亮点:使用 Canvas 2D 实现实时光线追踪引擎,支持镜面反射、折射和光爆粒子特效。游戏画面作为底层,悬浮HUD叠加其上。

typescript 复制代码
// components/GameCanvas.ets
import { Canvas, CanvasRenderingContext2D } from '@kit.ArkUI';

/**
 * 镜面类型枚举
 */
export enum MirrorType {
  REFLECT = 'reflect',    // 反射镜
  REFRACT = 'refract',    // 折射镜(棱镜)
  SPLIT = 'split',        // 分光器
  PRISM = 'prism'         // 三棱镜(彩虹分光)
}

/**
 * 光线数据结构
 */
interface LightRay {
  x: number;
  y: number;
  angle: number;
  color: string;
  intensity: number;  // 0-1,影响光效亮度
  bounces: number;    // 反射次数,防止无限递归
}

/**
 * 镜面对象
 */
interface Mirror {
  id: string;
  x: number;
  y: number;
  width: number;
  height: number;
  angle: number;      // 旋转角度
  type: MirrorType;
  isDragging: boolean;
}

@Component
export struct GameCanvas {
  private canvasRef: CanvasRenderingContext2D | null = null;
  private animationId: number = 0;
  
  @State mirrors: Mirror[] = [];
  @State lightSource: LightRay = { x: 100, y: 300, angle: 0, color: '#FFD700', intensity: 1.0, bounces: 0 };
  @State targetLit: boolean = false;
  @State levelTheme: string = '#1a0a2e';  // 当前关卡主题色(地牢紫)
  
  // 关卡配置
  private levels = [
    { theme: '#1a0a2e', name: '暗影地牢', lightColor: '#FFD700' },   // 金黄光
    { theme: '#0a1a0e', name: '翡翠森林', lightColor: '#00FF88' },   // 翠绿光
    { theme: '#0a0a1e', name: '冰封峡谷', lightColor: '#00CCFF' },   // 冰蓝光
    { theme: '#1e0a0a', name: '熔岩核心', lightColor: '#FF4400' }    // 熔岩红
  ];

  aboutToAppear(): void {
    this.loadLevel(0);
    this.startGameLoop();
  }

  aboutToDisappear(): void {
    if (this.animationId) {
      cancelAnimationFrame(this.animationId);
    }
  }

  /**
   * 加载关卡数据
   */
  private loadLevel(levelIndex: number): void {
    const level = this.levels[levelIndex];
    this.levelTheme = level.theme;
    this.lightSource.color = level.lightColor;
    
    // 同步到全局,供HUD和光效层使用
    AppStorage.setOrCreate('game_theme_color', level.theme);
    AppStorage.setOrCreate('game_light_color', level.lightColor);
    
    // 初始化镜面布局(简化示例)
    this.mirrors = [
      { id: 'm1', x: 300, y: 200, width: 80, height: 10, angle: 45, type: MirrorType.REFLECT, isDragging: false },
      { id: 'm2', x: 500, y: 400, width: 80, height: 10, angle: -30, type: MirrorType.REFRACT, isDragging: false }
    ];
  }

  /**
   * 游戏主循环:60fps光线追踪渲染
   */
  private startGameLoop(): void {
    const loop = () => {
      this.renderFrame();
      this.animationId = requestAnimationFrame(loop);
    };
    this.animationId = requestAnimationFrame(loop);
  }

  /**
   * 渲染单帧画面
   */
  private renderFrame(): void {
    if (!this.canvasRef) return;
    
    const ctx = this.canvasRef;
    const width = ctx.canvas.width;
    const height = ctx.canvas.height;

    // 1. 清空画布(使用关卡主题色作为背景)
    ctx.fillStyle = this.levelTheme;
    ctx.fillRect(0, 0, width, height);

    // 2. 绘制环境光晕(沉浸光感基础层)
    this.drawAmbientGlow(ctx, width, height);

    // 3. 绘制目标区域
    this.drawTarget(ctx);

    // 4. 绘制镜面
    this.mirrors.forEach(mirror => this.drawMirror(ctx, mirror));

    // 5. 光线追踪渲染
    this.traceLightRay(ctx, this.lightSource);

    // 6. 检测目标是否被点亮
    this.checkTargetLit();
  }

  /**
   * 绘制环境光晕(沉浸光感核心)
   * 根据光源位置和强度,在背景上渲染动态光晕
   */
  private drawAmbientGlow(ctx: CanvasRenderingContext2D, width: number, height: number): void {
    // 主光源光晕
    const gradient = ctx.createRadialGradient(
      this.lightSource.x, this.lightSource.y, 0,
      this.lightSource.x, this.lightSource.y, 300
    );
    gradient.addColorStop(0, this.lightSource.color + '40');  // 25%透明度
    gradient.addColorStop(0.5, this.lightSource.color + '10'); // 6%透明度
    gradient.addColorStop(1, 'transparent');

    ctx.fillStyle = gradient;
    ctx.fillRect(0, 0, width, height);

    // 镜面反射光点(增强空间感)
    this.mirrors.forEach(mirror => {
      if (mirror.type === MirrorType.REFLECT) {
        const glowGradient = ctx.createRadialGradient(
          mirror.x, mirror.y, 0,
          mirror.x, mirror.y, 60
        );
        glowGradient.addColorStop(0, '#FFFFFF20');
        glowGradient.addColorStop(1, 'transparent');
        ctx.fillStyle = glowGradient;
        ctx.beginPath();
        ctx.arc(mirror.x, mirror.y, 60, 0, Math.PI * 2);
        ctx.fill();
      }
    });
  }

  /**
   * 光线追踪核心算法
   * 递归计算光线与镜面的交点和反射方向
   */
  private traceLightRay(ctx: CanvasRenderingContext2D, ray: LightRay): void {
    if (ray.bounces > 10 || ray.intensity < 0.1) return;

    // 计算光线终点(屏幕边界或镜面交点)
    const endPoint = this.calculateRayEnd(ray);
    
    // 绘制光线(带发光效果)
    ctx.save();
    ctx.shadowColor = ray.color;
    ctx.shadowBlur = 15 * ray.intensity;  // 光强影响发光半径
    ctx.strokeStyle = ray.color;
    ctx.lineWidth = 3 * ray.intensity;
    ctx.globalAlpha = ray.intensity;
    
    ctx.beginPath();
    ctx.moveTo(ray.x, ray.y);
    ctx.lineTo(endPoint.x, endPoint.y);
    ctx.stroke();
    ctx.restore();

    // 检测与镜面的交点
    const hitMirror = this.findMirrorIntersection(ray, endPoint);
    if (hitMirror) {
      // 计算反射/折射光线
      const newRay = this.calculateReflection(ray, hitMirror);
      this.traceLightRay(ctx, newRay);  // 递归追踪
    }
  }

  /**
   * 绘制镜面(玻璃拟态风格)
   */
  private drawMirror(ctx: CanvasRenderingContext2D, mirror: Mirror): void {
    ctx.save();
    ctx.translate(mirror.x, mirror.y);
    ctx.rotate(mirror.angle * Math.PI / 180);

    // 镜面主体(玻璃拟态效果)
    ctx.fillStyle = 'rgba(255,255,255,0.15)';
    ctx.strokeStyle = 'rgba(255,255,255,0.4)';
    ctx.lineWidth = 2;
    
    // 圆角矩形
    const r = 5;
    const w = mirror.width;
    const h = mirror.height;
    ctx.beginPath();
    ctx.moveTo(-w/2 + r, -h/2);
    ctx.lineTo(w/2 - r, -h/2);
    ctx.quadraticCurveTo(w/2, -h/2, w/2, -h/2 + r);
    ctx.lineTo(w/2, h/2 - r);
    ctx.quadraticCurveTo(w/2, h/2, w/2 - r, h/2);
    ctx.lineTo(-w/2 + r, h/2);
    ctx.quadraticCurveTo(-w/2, h/2, -w/2, h/2 - r);
    ctx.lineTo(-w/2, -h/2 + r);
    ctx.quadraticCurveTo(-w/2, -h/2, -w/2 + r, -h/2);
    ctx.closePath();
    
    ctx.fill();
    ctx.stroke();

    // 镜面高光(增强玻璃质感)
    ctx.fillStyle = 'rgba(255,255,255,0.3)';
    ctx.fillRect(-w/2 + 5, -h/2 + 2, w - 10, h/2 - 2);

    // 类型标识
    ctx.rotate(-mirror.angle * Math.PI / 180);
    ctx.fillStyle = '#FFFFFF';
    ctx.font = '10px sans-serif';
    ctx.textAlign = 'center';
    const typeLabels = {
      [MirrorType.REFLECT]: '反射',
      [MirrorType.REFRACT]: '折射',
      [MirrorType.SPLIT]: '分光',
      [MirrorType.PRISM]: '棱镜'
    };
    ctx.fillText(typeLabels[mirror.type], 0, -15);

    ctx.restore();
  }

  /**
   * 绘制目标区域
   */
  private drawTarget(ctx: CanvasRenderingContext2D): void {
    const targetX = 700;
    const targetY = 300;
    const targetRadius = 30;

    // 目标光晕(未点亮时暗淡,点亮时明亮)
    const glowIntensity = this.targetLit ? 1.0 : 0.3;
    const gradient = ctx.createRadialGradient(
      targetX, targetY, 0,
      targetX, targetY, targetRadius * 2
    );
    gradient.addColorStop(0, this.targetLit ? '#00FF88' : '#666666');
    gradient.addColorStop(1, 'transparent');

    ctx.fillStyle = gradient;
    ctx.globalAlpha = glowIntensity;
    ctx.beginPath();
    ctx.arc(targetX, targetY, targetRadius * 2, 0, Math.PI * 2);
    ctx.fill();

    // 目标核心
    ctx.globalAlpha = 1.0;
    ctx.fillStyle = this.targetLit ? '#00FF88' : '#444444';
    ctx.beginPath();
    ctx.arc(targetX, targetY, targetRadius, 0, Math.PI * 2);
    ctx.fill();

    // 目标图标
    ctx.fillStyle = '#FFFFFF';
    ctx.font = '20px sans-serif';
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.fillText('★', targetX, targetY);
  }

  /**
   * 检查目标是否被光线命中
   */
  private checkTargetLit(): void {
    // 简化检测:判断光线终点是否在目标区域内
    // 实际应使用射线与圆的交点检测
    const targetX = 700;
    const targetY = 300;
    const targetRadius = 30;
    
    // 这里简化处理,实际通过光线追踪结果判断
    const wasLit = this.targetLit;
    this.targetLit = Math.random() > 0.95; // 模拟检测(实际应为真实物理计算)
    
    if (this.targetLit && !wasLit) {
      this.triggerLevelComplete();
    }
  }

  /**
   * 通关触发:光爆特效 + 悬浮HUD反馈
   */
  private triggerLevelComplete(): void {
    // 触发全局光爆事件
    AppStorage.setOrCreate('game_level_complete', Date.now());
    
    // 微震动反馈
    try {
      import('@kit.SensorServiceKit').then(sensor => {
        sensor.vibrator.startVibration({
          type: 'time',
          duration: 200
        }, { id: 0 });
      });
    } catch (error) {
      console.error('Vibration failed:', error);
    }
  }

  // 辅助方法(简化实现)
  private calculateRayEnd(ray: LightRay): { x: number; y: number } {
    const maxDistance = 1000;
    return {
      x: ray.x + Math.cos(ray.angle) * maxDistance,
      y: ray.y + Math.sin(ray.angle) * maxDistance
    };
  }

  private findMirrorIntersection(ray: LightRay, endPoint: { x: number; y: number }): Mirror | null {
    // 简化:返回第一个镜面
    return this.mirrors[0] || null;
  }

  private calculateReflection(ray: LightRay, mirror: Mirror): LightRay {
    // 简化反射计算
    return {
      x: mirror.x,
      y: mirror.y,
      angle: ray.angle + Math.PI,
      color: ray.color,
      intensity: ray.intensity * 0.8,  // 每次反射衰减20%
      bounces: ray.bounces + 1
    };
  }

  build() {
    Canvas(this.canvasRef)
      .width('100%')
      .height('100%')
      .backgroundColor(this.levelTheme)
      .onReady((context) => {
        this.canvasRef = context;
      })
      .onTouch((event) => {
        // 处理镜面拖拽交互
        this.handleTouch(event);
      })
  }

  private handleTouch(event: TouchEvent): void {
    // 镜面拖拽逻辑(简化)
    const touch = event.touches[0];
    // 检测触摸点是否在镜面上,启动拖拽...
  }
}

4.2 游戏悬浮HUD:道具栏与操作面板

代码亮点:将悬浮导航改造为游戏HUD,支持道具拖拽、透明度动态调节、长按展开详细面板。玻璃拟态材质与游戏背景光效深度融合。

typescript 复制代码
// components/GameHUD.ets
import { window } from '@kit.ArkUI';

/**
 * 道具配置
 */
interface GameItem {
  id: string;
  name: string;
  icon: Resource;
  count: number;
  type: string;
  cooldown: number;  // 冷却时间(秒)
}

/**
 * HUD透明度档位
 */
export enum HUDTransparency {
  STEALTH = 0.30,   // 潜行模式:极低透明度,不干扰游戏画面
  BALANCED = 0.55,  // 平衡模式:适中透明度
  VISIBLE = 0.80    // 高可见模式:清晰显示道具信息
}

@Component
export struct GameHUD {
  @State selectedItem: string = '';
  @State hudTransparency: number = HUDTransparency.BALANCED;
  @State isExpanded: boolean = false;  // 展开详细面板
  @State bottomAvoidHeight: number = 0;
  @State gameThemeColor: string = '#1a0a2e';
  @State gameLightColor: string = '#FFD700';
  @State isLevelComplete: boolean = false;

  // 道具背包
  private inventory: GameItem[] = [
    { id: 'mirror', name: '反射镜', icon: $r('app.media.ic_mirror'), count: 5, type: 'reflect', cooldown: 0 },
    { id: 'prism', name: '三棱镜', icon: $r('app.media.ic_prism'), count: 3, type: 'prism', cooldown: 5 },
    { id: 'splitter', name: '分光器', icon: $r('app.media.ic_splitter'), count: 2, type: 'split', cooldown: 8 },
    { id: 'lens', name: '聚光镜', icon: $r('app.media.ic_lens'), count: 1, type: 'focus', cooldown: 12 }
  ];

  aboutToAppear(): void {
    this.getBottomAvoidArea();
    
    // 监听游戏状态变化
    AppStorage.watch('game_theme_color', (color: string) => {
      this.gameThemeColor = color;
    });
    AppStorage.watch('game_light_color', (color: string) => {
      this.gameLightColor = color;
    });
    AppStorage.watch('game_level_complete', () => {
      this.isLevelComplete = true;
      this.triggerCompletionEffect();
    });
  }

  private async getBottomAvoidArea(): Promise<void> {
    try {
      const mainWindow = await window.getLastWindow();
      const avoidArea = mainWindow.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR);
      this.bottomAvoidHeight = avoidArea.bottomRect.height;
    } catch (error) {
      console.error('Failed to get avoid area:', error);
    }
  }

  /**
   * 通关特效:HUD光爆 + 悬浮面板展开
   */
  private triggerCompletionEffect(): void {
    // 临时提高透明度展示通关信息
    this.hudTransparency = HUDTransparency.VISIBLE;
    this.isExpanded = true;
    
    // 3秒后恢复
    setTimeout(() => {
      this.isLevelComplete = false;
      this.isExpanded = false;
      this.hudTransparency = HUDTransparency.BALANCED;
    }, 3000);
  }

  build() {
    Stack({ alignContent: Alignment.Bottom }) {
      // 游戏内容区域(由父组件传入Canvas)
      Column() {
        this.gameContentBuilder()
      }
      .width('100%')
      .height('100%')
      // 底部预留HUD空间
      .padding({ bottom: this.bottomAvoidHeight + 100 })

      // 通关光爆覆盖层(全屏特效)
      if (this.isLevelComplete) {
        this.buildCompletionOverlay()
      }

      // 悬浮HUD主面板
      Column() {
        // 玻璃拟态背景(多层叠加实现游戏级光感)
        Stack() {
          // 底层:动态主题色晕染
          Column()
            .width('100%')
            .height('100%')
            .backgroundColor(this.gameThemeColor)
            .opacity(0.3)
            .blur(60)

          // 中层:毛玻璃模糊
          Column()
            .width('100%')
            .height('100%')
            .backgroundBlurStyle(BlurStyle.COMPONENT_THICK)
            .opacity(this.hudTransparency)

          // 顶层:光源色高光(与游戏光线同步)
          Column()
            .width('100%')
            .height('100%')
            .linearGradient({
              direction: GradientDirection.Top,
              colors: [
                [this.gameLightColor + '30', 0.0],  // 光源色顶部高光
                ['transparent', 0.6],
                [this.gameThemeColor + '20', 1.0]   // 主题色底部晕染
              ]
            })
        }
        .width('100%')
        .height('100%')
        .borderRadius(28)  // 大圆角,更柔和的游戏风格
        .shadow({
          radius: 25,
          color: this.gameLightColor + '40',  // 光源色阴影
          offsetX: 0,
          offsetY: -6
        })
        // 通关时的脉冲光效
        .animation({
          duration: this.isLevelComplete ? 800 : 300,
          curve: Curve.Spring,
          iterations: this.isLevelComplete ? 3 : 1
        })

        // HUD内容区
        Column() {
          // 顶部信息条(关卡/分数)
          this.buildInfoBar()

          // 道具栏(核心交互区)
          this.buildItemBar()

          // 展开面板(详细道具信息/设置)
          if (this.isExpanded) {
            this.buildExpandedPanel()
          }
        }
        .width('100%')
        .height('100%')
        .padding(12)
      }
      .width('94%')  // 更宽的悬浮面板,适合游戏操作
      .height(this.isExpanded ? 200 : 90)
      .margin({ 
        bottom: this.bottomAvoidHeight + 12, 
        left: '3%', 
        right: '3%' 
      })
      .animation({
        duration: 400,
        curve: Curve.Spring,
        iterations: 1
      })
      // 长按展开/收起
      .gesture(
        LongPressGesture({ duration: 400 })
          .onAction(() => {
            if (!this.isLevelComplete) {
              this.isExpanded = !this.isExpanded;
            }
          })
      )
      // 双指捏合调节透明度
      .gesture(
        PinchGesture()
          .onActionUpdate((event: GestureEvent) => {
            const newScale = event.scale;
            if (newScale > 1.2) {
              this.hudTransparency = Math.min(this.hudTransparency + 0.05, HUDTransparency.VISIBLE);
            } else if (newScale < 0.8) {
              this.hudTransparency = Math.max(this.hudTransparency - 0.05, HUDTransparency.STEALTH);
            }
          })
      )
    }
    .width('100%')
    .height('100%')
  }

  /**
   * 顶部信息条
   */
  @Builder
  buildInfoBar(): void {
    Row() {
      // 关卡名称
      Row({ space: 6 }) {
        Image($r('app.media.ic_level'))
          .width(16)
          .height(16)
          .fillColor(this.gameLightColor)
        
        Text('暗影地牢 · 第3关')
          .fontSize(13)
          .fontColor('#FFFFFF')
          .fontWeight(FontWeight.Medium)
      }

      // 分数/星级
      Row({ space: 4 }) {
        ForEach([1, 2, 3], (star: number) => {
          Image(star <= 2 ? $r('app.media.ic_star_filled') : $r('app.media.ic_star_empty'))
            .width(18)
            .height(18)
            .fillColor(star <= 2 ? this.gameLightColor : '#FFFFFF40')
        })
      }

      // 设置按钮
      Button() {
        Image($r('app.media.ic_settings'))
          .width(20)
          .height(20)
          .fillColor('#FFFFFF')
      }
      .type(ButtonType.Circle)
      .backgroundColor('rgba(255,255,255,0.1)')
      .width(32)
      .height(32)
      .onClick(() => {
        // 打开游戏设置
      })
    }
    .width('100%')
    .justifyContent(FlexAlign.SpaceBetween)
    .padding({ left: 8, right: 8, bottom: 8 })
  }

  /**
   * 道具栏(支持拖拽和点击)
   */
  @Builder
  buildItemBar(): void {
    Row({ space: 12 }) {
      ForEach(this.inventory, (item: GameItem) => {
        Column() {
          Stack() {
            // 道具图标背景(光晕效果)
            if (this.selectedItem === item.id) {
              Column()
                .width(56)
                .height(56)
                .backgroundColor(this.gameLightColor)
                .borderRadius(16)
                .opacity(0.3)
                .blur(8)
                .animation({
                  duration: 600,
                  curve: Curve.EaseInOut,
                  iterations: -1,
                  playMode: PlayMode.Alternate
                })
            }

            // 道具图标
            Image(item.icon)
              .width(44)
              .height(44)
              .fillColor(this.selectedItem === item.id ? this.gameLightColor : '#FFFFFF')
              .opacity(item.count > 0 ? 1.0 : 0.3)

            // 数量徽章
            if (item.count > 0) {
              Column() {
                Text(`${item.count}`)
                  .fontSize(10)
                  .fontColor('#FFFFFF')
                  .fontWeight(FontWeight.Bold)
              }
              .width(18)
              .height(18)
              .backgroundColor('#FF4444')
              .borderRadius(9)
              .position({ x: 32, y: -4 })
              .shadow({ radius: 4, color: '#FF4444' })
            }

            // 冷却遮罩
            if (item.cooldown > 0) {
              Column()
                .width(44)
                .height(44)
                .backgroundColor('rgba(0,0,0,0.6)')
                .borderRadius(12)
                .justifyContent(FlexAlign.Center)
              
              Text(`${item.cooldown}s`)
                .fontSize(12)
                .fontColor('#FFFFFF')
            }
          }
          .width(56)
          .height(56)

          Text(item.name)
            .fontSize(10)
            .fontColor(this.selectedItem === item.id ? this.gameLightColor : '#FFFFFF80')
            .margin({ top: 4 })
        }
        .width(64)
        .height(76)
        .onClick(() => {
          if (item.count > 0 && item.cooldown === 0) {
            this.selectedItem = this.selectedItem === item.id ? '' : item.id;
            this.triggerSelectFeedback();
          }
        })
        // 拖拽手势:将道具拖入游戏画面
        .gesture(
          PanGesture()
            .onActionUpdate((event: GestureEvent) => {
              // 更新拖拽位置,与GameCanvas联动
              AppStorage.setOrCreate('drag_item', {
                id: item.id,
                offsetX: event.offsetX,
                offsetY: event.offsetY
              });
            })
            .onActionEnd(() => {
              AppStorage.setOrCreate('drag_item', null);
            })
        )
      })
    }
    .width('100%')
    .justifyContent(FlexAlign.SpaceAround)
    .padding({ top: 4, bottom: 4 })
  }

  /**
   * 展开面板(详细说明/快捷设置)
   */
  @Builder
  buildExpandedPanel(): void {
    Column({ space: 12 }) {
      // 透明度快捷调节
      Row({ space: 8 }) {
        Text('HUD透明度')
          .fontSize(12)
          .fontColor('#FFFFFF80')
          .width(70)

        Button('潜行')
          .type(ButtonType.Capsule)
          .fontSize(11)
          .backgroundColor(this.hudTransparency === HUDTransparency.STEALTH ? this.gameLightColor : 'rgba(255,255,255,0.1)')
          .onClick(() => this.hudTransparency = HUDTransparency.STEALTH)

        Button('平衡')
          .type(ButtonType.Capsule)
          .fontSize(11)
          .backgroundColor(this.hudTransparency === HUDTransparency.BALANCED ? this.gameLightColor : 'rgba(255,255,255,0.1)')
          .onClick(() => this.hudTransparency = HUDTransparency.BALANCED)

        Button('高亮')
          .type(ButtonType.Capsule)
          .fontSize(11)
          .backgroundColor(this.hudTransparency === HUDTransparency.VISIBLE ? this.gameLightColor : 'rgba(255,255,255,0.1)')
          .onClick(() => this.hudTransparency = HUDTransparency.VISIBLE)
      }

      // 画质设置
      Row({ space: 8 }) {
        Text('光效质量')
          .fontSize(12)
          .fontColor('#FFFFFF80')
          .width(70)

        Slider({
          value: AppStorage.get<number>('game_quality') || 2,
          min: 1,
          max: 3,
          step: 1,
          style: SliderStyle.InSet
        })
          .width(120)
          .selectedColor(this.gameLightColor)
          .onChange((value: number) => {
            AppStorage.setOrCreate('game_quality', value);
          })
      }
    }
    .width('100%')
    .padding({ top: 12 })
    .border({
      width: { top: 1 },
      color: 'rgba(255,255,255,0.1)'
    })
  }

  /**
   * 通关光爆覆盖层
   */
  @Builder
  buildCompletionOverlay(): void {
    Column() {
      // 全屏白光爆
      Column()
        .width('100%')
        .height('100%')
        .backgroundColor('#FFFFFF')
        .opacity(0.6)
        .animation({
          duration: 500,
          curve: Curve.EaseOut,
          iterations: 1
        })

      // 通关文字
      Column() {
        Text('关卡完成!')
          .fontSize(48)
          .fontWeight(FontWeight.Bold)
          .fontColor('#FFFFFF')
          .shadow({ radius: 20, color: this.gameLightColor })

        Text('光线成功点亮目标')
          .fontSize(16)
          .fontColor('#FFFFFF80')
          .margin({ top: 12 })

        Button('下一关 →')
          .type(ButtonType.Capsule)
          .fontSize(16)
          .fontColor('#FFFFFF')
          .backgroundColor(this.gameLightColor)
          .width(160)
          .height(48)
          .margin({ top: 24 })
          .onClick(() => {
            // 加载下一关
            AppStorage.setOrCreate('game_next_level', Date.now());
          })
      }
    }
    .width('100%')
    .height('100%')
    .backgroundColor('rgba(0,0,0,0.7)')
    .justifyContent(FlexAlign.Center)
    .animation({
      duration: 300,
      curve: Curve.EaseOut
    })
  }

  /**
   * 道具选中反馈
   */
  private triggerSelectFeedback(): void {
    try {
      import('@kit.SensorServiceKit').then(sensor => {
        sensor.vibrator.startVibration({
          type: 'time',
          duration: 30
        }, { id: 0 });
      });
    } catch (error) {
      console.error('Haptic feedback failed:', error);
    }
  }

  // 游戏内容构建器(由外部传入GameCanvas)
  @BuilderParam gameContentBuilder: () => void = this.defaultGameContentBuilder;

  @Builder
  defaultGameContentBuilder(): void {
    Column() {
      Text('游戏画面区域')
        .fontSize(16)
        .fontColor('#FFFFFF40')
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

4.3 关卡管理器与自适应画质

代码亮点:根据设备性能自动调整光效复杂度,低端设备关闭动态光晕和粒子特效,确保流畅度。

typescript 复制代码
// utils/GameQualityManager.ets
import { display } from '@kit.ArkUI';

export enum QualityLevel {
  LOW = 1,      // 低画质:关闭动态光晕,简化反射
  MEDIUM = 2,   // 中画质:标准光效,单次反射
  HIGH = 3      // 高画质:全特效,多次反射+粒子
}

export class GameQualityManager {
  private static instance: GameQualityManager;
  private currentQuality: QualityLevel = QualityLevel.MEDIUM;
  private devicePerformance: number = 0;

  static getInstance(): GameQualityManager {
    if (!GameQualityManager.instance) {
      GameQualityManager.instance = new GameQualityManager();
    }
    return GameQualityManager.instance;
  }

  /**
   * 自动检测设备性能并设置画质
   */
  async autoDetectQuality(): Promise<void> {
    try {
      // 获取屏幕刷新率作为性能指标
      const displayInfo = display.getDefaultDisplaySync();
      const refreshRate = displayInfo.refreshRate;
      const width = displayInfo.width;
      const height = displayInfo.height;

      // 计算性能分数
      this.devicePerformance = refreshRate * (width * height) / 1000000;

      if (this.devicePerformance > 200) {
        this.currentQuality = QualityLevel.HIGH;
      } else if (this.devicePerformance > 100) {
        this.currentQuality = QualityLevel.MEDIUM;
      } else {
        this.currentQuality = QualityLevel.LOW;
      }

      AppStorage.setOrCreate('game_quality', this.currentQuality);
      console.info(`Auto quality set to: ${this.currentQuality}, performance: ${this.devicePerformance}`);
    } catch (error) {
      console.error('Failed to detect quality:', error);
      this.currentQuality = QualityLevel.MEDIUM;
    }
  }

  /**
   * 获取当前画质配置
   */
  getQualityConfig(): {
    enableGlow: boolean;
    maxBounces: number;
    particleCount: number;
    shadowQuality: number;
  } {
    const configs = {
      [QualityLevel.LOW]: {
        enableGlow: false,
        maxBounces: 2,
        particleCount: 0,
        shadowQuality: 0
      },
      [QualityLevel.MEDIUM]: {
        enableGlow: true,
        maxBounces: 5,
        particleCount: 50,
        shadowQuality: 1
      },
      [QualityLevel.HIGH]: {
        enableGlow: true,
        maxBounces: 10,
        particleCount: 200,
        shadowQuality: 2
      }
    };

    return configs[this.currentQuality];
  }

  setQuality(level: QualityLevel): void {
    this.currentQuality = level;
    AppStorage.setOrCreate('game_quality', level);
  }

  getCurrentQuality(): QualityLevel {
    return this.currentQuality;
  }
}

4.4 游戏主页面:全屏沉浸与光效联动

typescript 复制代码
// pages/GamePage.ets
import { GameCanvas } from '../components/GameCanvas';
import { GameHUD } from '../components/GameHUD';
import { GameQualityManager } from '../utils/GameQualityManager';

@Entry
@Component
struct GamePage {
  @State gameTheme: string = '#1a0a2e';
  @State gameLight: string = '#FFD700';

  aboutToAppear(): void {
    // 自动检测设备性能
    GameQualityManager.getInstance().autoDetectQuality();
    
    // 监听主题色变化
    AppStorage.watch('game_theme_color', (color: string) => {
      this.gameTheme = color;
    });
    AppStorage.watch('game_light_color', (color: string) => {
      this.gameLight = color;
    });
  }

  build() {
    Stack() {
      // 第一层:动态环境光背景(与游戏关卡同步)
      this.buildDynamicBackground()

      // 第二层:游戏Canvas(光线追踪核心)
      GameCanvas()
        .width('100%')
        .height('100%')

      // 第三层:游戏HUD悬浮面板
      GameHUD({
        gameContentBuilder: () => {
          // HUD内部已包含Canvas,这里为空占位
        }
      })

      // 第四层:顶部状态栏(沉浸光感标题栏)
      this.buildImmersiveStatusBar()
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#000000')
    // 全屏沉浸:扩展至所有安全区
    .expandSafeArea(
      [SafeAreaType.SYSTEM],
      [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM, SafeAreaEdge.START, SafeAreaEdge.END]
    )
  }

  /**
   * 动态环境光背景
   * 根据游戏关卡主题色渲染全屏光晕
   */
  @Builder
  buildDynamicBackground(): void {
    Column() {
      // 主光晕(跟随光源位置)
      Column()
        .width(600)
        .height(600)
        .backgroundColor(this.gameLight)
        .blur(200)
        .opacity(0.15)
        .position({ x: '20%', y: '30%' })
        .animation({
          duration: 8000,
          curve: Curve.EaseInOut,
          iterations: -1,
          playMode: PlayMode.Alternate
        })
        .scale({ x: 1.5, y: 1.5 })

      // 环境色晕染
      Column()
        .width('100%')
        .height('100%')
        .backgroundColor(this.gameTheme)
        .opacity(0.8)

      // 底部反射光
      Column()
        .width('100%')
        .height(300)
        .backgroundColor(this.gameLight)
        .opacity(0.05)
        .blur(100)
        .position({ x: 0, y: '70%' })
        .linearGradient({
          direction: GradientDirection.Top,
          colors: [
            [this.gameLight, 0.0],
            ['transparent', 1.0]
          ]
        })
    }
    .width('100%')
    .height('100%')
  }

  /**
   * 沉浸光感状态栏
   * 游戏内自定义,显示FPS、电量、时间
   */
  @Builder
  buildImmersiveStatusBar(): void {
    Row() {
      // FPS显示
      Row({ space: 4 }) {
        Column()
          .width(8)
          .height(8)
          .backgroundColor('#00FF88')
          .borderRadius(4)
        
        Text('60 FPS')
          .fontSize(12)
          .fontColor('#FFFFFF80')
      }

      // 电量
      Row({ space: 4 }) {
        Image($r('app.media.ic_battery'))
          .width(16)
          .height(16)
          .fillColor('#FFFFFF60')
        
        Text('85%')
          .fontSize(12)
          .fontColor('#FFFFFF80')
      }

      // 时间
      Text('14:32')
        .fontSize(12)
        .fontColor('#FFFFFF80')
    }
    .width('100%')
    .height(40)
    .padding({ left: 16, right: 16 })
    .justifyContent(FlexAlign.SpaceBetween)
    .position({ x: 0, y: 0 })
    // 玻璃拟态背景
    .backgroundBlurStyle(BlurStyle.REGULAR)
    .backgroundColor('rgba(0,0,0,0.3)')
  }
}

五、关键技术总结

5.1 游戏场景下的悬浮导航适配

技术点 游戏化改造 API/方法
导航栏定位 游戏HUD道具栏 底部悬浮 + 拖拽交互
透明度控制 潜行/平衡/高亮三档 双指捏合手势调节
交互反馈 道具选中光效 + 微震动 vibrator.startVibration
展开面板 详细道具信息/画质设置 LongPressGesture 触发

5.2 沉浸光感在游戏中的创新应用

  1. 动态环境光晕 :Canvas 2D createRadialGradient + 关卡主题色,实时渲染与游戏画面同步的光效背景
  2. 光源色同步:HUD阴影、高光、选中状态均使用游戏内光源颜色,实现"内外一致"的沉浸体验
  3. 通关光爆特效:全屏白光覆盖层 + 脉冲动画 + 震动反馈,强化成就感
  4. 自适应画质:根据设备刷新率和分辨率自动调整光效复杂度,平衡视觉与性能

5.3 性能优化策略

typescript 复制代码
// 1. 画质分级:低端设备关闭高消耗特效
const quality = GameQualityManager.getInstance().getQualityConfig();
if (!quality.enableGlow) {
  // 跳过环境光晕渲染
}

// 2. 帧率控制:游戏循环使用 requestAnimationFrame
const loop = () => {
  renderFrame();
  requestAnimationFrame(loop);
};

// 3. 离屏渲染:复杂光效预渲染到离屏Canvas
const offscreenCanvas = new OffscreenCanvas(width, height);

// 4. 对象池:复用粒子对象,减少GC
const particlePool: Particle[] = [];

六、调试与多设备适配

6.1 真机调试要点

  1. 光效性能:在 120Hz 设备上测试高画质模式,确保光线追踪不丢帧
  2. 手势冲突 :游戏内禁用系统手势,通过 setWindowGestureDisabled(true) 防止误触退出
  3. 悬浮HUD位置:不同屏幕比例(手机 19.5:9 / 平板 4:3 / PC 16:9)下测试HUD布局

6.2 多设备适配策略

设备类型 悬浮HUD适配 沉浸光感适配
手机 底部窄条,左右留白 3% 全屏光晕,强度适中
平板 底部宽条,左右留白 10% 更大范围光晕,支持分屏
PC 可拖拽浮动窗口,自由定位 窗口边缘发光,多窗口光效同步

七、总结与展望

本文基于 HarmonyOS 6(API 23)的 悬浮导航沉浸光感 特性,完整实战了一款"光影迷宫"解谜游戏。核心创新点总结:

  1. 游戏化HUD改造:将传统悬浮导航改造为道具栏+操作面板,支持拖拽交互、双指捏合调节透明度、长按展开详细面板,实现"不干扰游戏画面、随时快速操作"的设计理念
  2. Canvas 2D 光线追踪:自研实时光线渲染引擎,支持反射/折射/分光物理模拟,光源色与系统光效深度联动
  3. 自适应画质系统:根据设备性能自动分级(低/中/高),确保从低端手机到高端PC的流畅体验
  4. 全屏沉浸架构 :通过 setWindowLayoutFullScreen + expandSafeArea + 自定义状态栏,实现真正的"无边界"游戏体验

未来扩展方向

  • 接入 Face AR:通过面部表情控制光线角度(挑眉旋转镜面、张嘴改变光色)
  • 接入 Body AR:手势识别直接拖拽镜面,实现"隔空操作"的科幻体验
  • HarmonyOS PC 多窗口:主游戏窗口 + 浮动策略窗口,光效跨窗口同步
  • 分布式对战:通过分布式软总线实现双人协作解谜,双方光效实时同步

转载自:https://blog.csdn.net/u014727709/article/details/151688885

欢迎 👍点赞✍评论⭐收藏,欢迎指正

相关推荐
上海云盾-小余2 小时前
UDP 洪水 DDoS 常态化攻击下,游戏与短剧服务器防护选型指南
游戏·udp·ddos
南村群童欺我老无力.2 小时前
鸿蒙ForEach渲染列表的唯一性约束与性能优化
华为·性能优化·harmonyos
HwJack202 小时前
HarmonyOS开发玩透 AR 虚拟相机位姿与渲染流水线
数码相机·ar·harmonyos
IntMainJhy2 小时前
Flutter 三方库 ImagePicker 的鸿蒙化适配与实战指南(相机/相册/多图选择全实现)
数码相机·flutter·harmonyos
说再见再也见不到2 小时前
华为交换机端口隔离(port-isolate)
linux·服务器·网络·华为·交换机·端口隔离·port-isolate
南村群童欺我老无力.2 小时前
鸿蒙中Image图片加载失败与资源适配
华为·harmonyos
木斯佳2 小时前
HarmonyOS 纸感交互实战:把天气卡片做成便利贴撕下效果
华为·交互·harmonyos
南村群童欺我老无力.2 小时前
鸿蒙开发中Scroll容器的嵌套冲突与滚动穿透
华为·harmonyos
IntMainJhy2 小时前
Flutter 三方库 SecureStorage 加密存储鸿蒙化适配与实战指南(加密读写+批量操作全覆盖)
flutter·华为·harmonyos