文章目录
-
- 每日一句正能量
- [一、前言:当游戏遇上HarmonyOS 6新特性](#一、前言:当游戏遇上HarmonyOS 6新特性)
- 二、游戏架构设计
-
- [2.1 技术选型](#2.1 技术选型)
- [2.2 游戏核心机制](#2.2 游戏核心机制)
- 三、核心代码实战
-
- [3.1 游戏主场景与沉浸窗口配置](#3.1 游戏主场景与沉浸窗口配置)
- [3.2 游戏核心渲染引擎(GameEngine.ets)](#3.2 游戏核心渲染引擎(GameEngine.ets))
- [3.3 游戏悬浮控制盘(GameFloatNav.ets)](#3.3 游戏悬浮控制盘(GameFloatNav.ets))
- [3.4 游戏主页面(GamePage.ets)](#3.4 游戏主页面(GamePage.ets))
- [3.5 游戏光效同步控制器](#3.5 游戏光效同步控制器)
- 四、关键技术总结
-
- [4.1 游戏场景下的悬浮导航设计原则](#4.1 游戏场景下的悬浮导航设计原则)
- [4.2 沉浸光感在游戏中的创新应用](#4.2 沉浸光感在游戏中的创新应用)
- [4.3 性能优化策略](#4.3 性能优化策略)
- 五、调试与发布建议
- 六、结语

每日一句正能量
我始终相信努力奋斗的意义,因为我想给自己一个牛逼的机会,趁自己还年轻;因为我必须给自己一个交代。因为我就是那么一个老掉牙的人,我相信梦想,我相信温暖,我相信理想。我相信我的选择不会错,我相信我的梦想不会错,我相信遗憾比失败更可怕。早安!
一、前言:当游戏遇上HarmonyOS 6新特性
HarmonyOS 6(API 23)带来的**悬浮导航(Float Navigation)与沉浸光感(Immersive Light Effects)**不仅是系统UI的革新,更为游戏开发提供了全新的交互与视觉维度。传统游戏中的固定UI会遮挡游戏画面,破坏沉浸感;而HarmonyOS 6的悬浮导航允许UI元素以半透明、可交互的方式悬浮于游戏场景之上,配合沉浸光感系统,能够实现游戏内光源与UI光效的完美联动。
本文将构建一款名为**"光影迷宫"**的2D解谜游戏,玩家通过控制光源在迷宫中探索,利用HarmonyOS 6的新特性实现:
- 游戏内悬浮控制盘:半透明悬浮导航替代传统虚拟摇杆
- 动态光效同步:玩家光源与UI光效、系统光效实时联动
- 玻璃拟态游戏UI:设置面板、道具栏采用毛玻璃效果
- P3广色域色彩:利用HarmonyOS 6的广色域支持呈现更绚丽的游戏画面
二、游戏架构设计
2.1 技术选型
| 模块 | 技术方案 | HarmonyOS 6特性 |
|---|---|---|
| 游戏渲染 | Canvas2D + ArkUI动画 | P3广色域渲染 |
| 物理引擎 | 自定义轻量级碰撞检测 | - |
| UI系统 | 悬浮导航组件 | Float Navigation |
| 光效系统 | 自定义着色器 + 系统光效 | Immersive Light |
| 状态管理 | AppStorage + 自定义事件总线 | 跨组件通信 |
2.2 游戏核心机制
玩家控制一个发光球体在暗黑迷宫中探索:
- 光源机制:球体发出动态光晕照亮周围,光强随时间衰减
- 悬浮导航:屏幕边缘悬浮透明控制盘,不遮挡游戏画面
- 光效联动:当玩家收集彩色水晶时,系统UI光效同步变化
- 解谜元素:利用镜面反射、光色混合等机制解开机关
三、核心代码实战
3.1 游戏主场景与沉浸窗口配置
代码亮点 :通过setWindowLayoutFullScreen和expandSafeArea实现真正的全屏游戏,内容延伸至状态栏和导航栏,配合深色背景让光效更加突出。
typescript
// entry/src/main/ets/ability/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 {
async onWindowStageCreate(windowStage: window.WindowStage): Promise<void> {
windowStage.loadContent('pages/GamePage', async (err) => {
if (err.code) {
console.error('Failed to load game content:', JSON.stringify(err));
return;
}
try {
const mainWindow = windowStage.getMainWindowSync();
// 1. 全屏布局,游戏内容延伸至所有边缘
await mainWindow.setWindowLayoutFullScreen(true);
// 2. 透明背景,允许游戏光效"穿透"到系统层
await mainWindow.setWindowBackgroundColor('#00000000');
// 3. 隐藏系统状态栏和导航栏,纯手势操作
await mainWindow.setWindowSystemBarEnable({
statusBar: false,
navigationBar: false
});
// 4. 设置屏幕常亮,避免游戏过程中息屏
await mainWindow.setWindowKeepScreenOn(true);
// 5. 高帧率模式(90/120Hz)
await mainWindow.setPreferredRefreshRate(120);
console.info('Game window configured for immersive experience');
} catch (error) {
console.error('Window setup failed:', (error as BusinessError).message);
}
});
}
onWindowStageDestroy(): void {
// 恢复屏幕自动息屏
console.info('Game ability destroyed');
}
}
3.2 游戏核心渲染引擎(GameEngine.ets)
代码亮点:基于Canvas2D构建轻量级游戏引擎,实现动态光源、粒子系统和碰撞检测,所有视觉元素均支持P3广色域色彩空间。
typescript
// entry/src/main/ets/engine/GameEngine.ets
import { drawing } from '@kit.ArkGraphics2D';
// 游戏实体接口
interface GameEntity {
x: number;
y: number;
width: number;
height: number;
color: string;
update(deltaTime: number): void;
render(ctx: CanvasRenderingContext2D): void;
}
// 光源配置
interface LightSource {
x: number;
y: number;
radius: number;
color: string;
intensity: number; // 0-1
pulseSpeed: number; // 呼吸速度
flickerIntensity: number; // 闪烁强度
}
// 玩家实体
class Player implements GameEntity {
x: number = 400;
y: number = 300;
width: number = 30;
height: number = 30;
color: string = '#00D2FF';
velocityX: number = 0;
velocityY: number = 0;
speed: number = 200; // 像素/秒
lightRadius: number = 150;
lightIntensity: number = 1.0;
// 光效动画参数
private pulsePhase: number = 0;
private flickerTimer: number = 0;
update(deltaTime: number): void {
// 更新位置
this.x += this.velocityX * deltaTime;
this.y += this.velocityY * deltaTime;
// 边界约束
this.x = Math.max(15, Math.min(785, this.x));
this.y = Math.max(15, Math.min(585, this.y));
// 光源呼吸动画
this.pulsePhase += deltaTime * 2;
const pulseFactor = Math.sin(this.pulsePhase) * 0.1 + 1;
// 随机闪烁模拟真实光源
this.flickerTimer += deltaTime;
if (this.flickerTimer > 0.05) {
this.flickerTimer = 0;
this.lightIntensity = 0.9 + Math.random() * 0.2;
}
// 动态光半径
this.lightRadius = 150 * pulseFactor * this.lightIntensity;
}
render(ctx: CanvasRenderingContext2D): void {
// 绘制外发光(多层光晕)
for (let i = 3; i > 0; i--) {
const gradient = ctx.createRadialGradient(
this.x, this.y, 0,
this.x, this.y, this.lightRadius * (i * 0.4 + 0.5)
);
gradient.addColorStop(0, this.color + Math.floor(this.lightIntensity * 80).toString(16).padStart(2, '0'));
gradient.addColorStop(1, 'transparent');
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.arc(this.x, this.y, this.lightRadius * (i * 0.4 + 0.5), 0, Math.PI * 2);
ctx.fill();
}
// 绘制实体核心(高亮)
ctx.fillStyle = '#FFFFFF';
ctx.shadowColor = this.color;
ctx.shadowBlur = 20;
ctx.beginPath();
ctx.arc(this.x, this.y, 8, 0, Math.PI * 2);
ctx.fill();
ctx.shadowBlur = 0;
// 绘制光晕核心
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.arc(this.x, this.y, 12, 0, Math.PI * 2);
ctx.fill();
}
}
// 水晶收集物
class Crystal implements GameEntity {
x: number;
y: number;
width: number = 24;
height: number = 24;
color: string;
collected: boolean = false;
private floatPhase: number = 0;
private rotation: number = 0;
constructor(x: number, y: number, color: string) {
this.x = x;
this.y = y;
this.color = color;
}
update(deltaTime: number): void {
if (this.collected) return;
this.floatPhase += deltaTime * 3;
this.rotation += deltaTime * 2;
}
render(ctx: CanvasRenderingContext2D): void {
if (this.collected) return;
const floatY = Math.sin(this.floatPhase) * 5;
ctx.save();
ctx.translate(this.x, this.y + floatY);
ctx.rotate(this.rotation);
// 水晶发光效果
ctx.shadowColor = this.color;
ctx.shadowBlur = 15;
// 绘制菱形水晶
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.moveTo(0, -12);
ctx.lineTo(10, 0);
ctx.lineTo(0, 12);
ctx.lineTo(-10, 0);
ctx.closePath();
ctx.fill();
// 内部高光
ctx.fillStyle = 'rgba(255,255,255,0.6)';
ctx.beginPath();
ctx.moveTo(0, -8);
ctx.lineTo(5, 0);
ctx.lineTo(0, 4);
ctx.lineTo(-5, 0);
ctx.closePath();
ctx.fill();
ctx.restore();
ctx.shadowBlur = 0;
}
}
// 墙壁(迷宫障碍物)
class Wall implements GameEntity {
x: number;
y: number;
width: number;
height: number;
color: string = '#2A2A3E';
constructor(x: number, y: number, w: number, h: number) {
this.x = x;
this.y = y;
this.width = w;
this.height = h;
}
update(_deltaTime: number): void {}
render(ctx: CanvasRenderingContext2D): void {
// 墙壁基础
ctx.fillStyle = this.color;
ctx.fillRect(this.x, this.y, this.width, this.height);
// 边缘高光(模拟环境光反射)
ctx.strokeStyle = 'rgba(255,255,255,0.05)';
ctx.lineWidth = 1;
ctx.strokeRect(this.x, this.y, this.width, this.height);
}
}
// 粒子系统(用于收集特效)
class ParticleSystem {
particles: Array<{
x: number; y: number;
vx: number; vy: number;
life: number; maxLife: number;
color: string; size: number;
}> = [];
emit(x: number, y: number, color: string, count: number = 10): void {
for (let i = 0; i < count; i++) {
const angle = (Math.PI * 2 * i) / count;
const speed = 50 + Math.random() * 100;
this.particles.push({
x, y,
vx: Math.cos(angle) * speed,
vy: Math.sin(angle) * speed,
life: 1.0,
maxLife: 0.5 + Math.random() * 0.5,
color,
size: 3 + Math.random() * 4
});
}
}
update(deltaTime: number): void {
this.particles = this.particles.filter(p => {
p.x += p.vx * deltaTime;
p.y += p.vy * deltaTime;
p.life -= deltaTime / p.maxLife;
p.vx *= 0.98; // 阻力
p.vy *= 0.98;
return p.life > 0;
});
}
render(ctx: CanvasRenderingContext2D): void {
this.particles.forEach(p => {
ctx.globalAlpha = p.life;
ctx.fillStyle = p.color;
ctx.shadowColor = p.color;
ctx.shadowBlur = 10;
ctx.beginPath();
ctx.arc(p.x, p.y, p.size * p.life, 0, Math.PI * 2);
ctx.fill();
});
ctx.globalAlpha = 1;
ctx.shadowBlur = 0;
}
}
// 游戏引擎主类
export class GameEngine {
private canvas: CanvasRenderingContext2D | null = null;
private lastTime: number = 0;
private isRunning: boolean = false;
// 游戏实体
player: Player = new Player();
walls: Wall[] = [];
crystals: Crystal[] = [];
particles: ParticleSystem = new ParticleSystem();
// 游戏状态
score: number = 0;
gameTime: number = 0;
collectedColors: Set<string> = new Set();
// 光效同步回调
onLightEffectChange?: (color: string, intensity: number) => void;
initialize(canvasContext: CanvasRenderingContext2D): void {
this.canvas = canvasContext;
this.setupLevel();
}
private setupLevel(): void {
// 初始化迷宫墙壁
this.walls = [
new Wall(100, 100, 200, 20),
new Wall(400, 50, 20, 200),
new Wall(600, 300, 150, 20),
new Wall(200, 400, 20, 150),
new Wall(500, 500, 200, 20),
// 边界墙
new Wall(0, 0, 800, 10),
new Wall(0, 590, 800, 10),
new Wall(0, 0, 10, 600),
new Wall(790, 0, 10, 600)
];
// 初始化水晶(不同颜色触发不同光效)
this.crystals = [
new Crystal(150, 200, '#FF6B6B'), // 红色 - 热情
new Crystal(650, 150, '#4ECDC4'), // 青色 - 宁静
new Crystal(300, 450, '#FFE66D'), // 黄色 - 活力
new Crystal(700, 500, '#95E1D3'), // 薄荷 - 清新
new Crystal(450, 300, '#F38181') // 粉红 - 浪漫
];
}
start(): void {
this.isRunning = true;
this.lastTime = Date.now();
this.gameLoop();
}
stop(): void {
this.isRunning = false;
}
private gameLoop(): void {
if (!this.isRunning || !this.canvas) return;
const currentTime = Date.now();
const deltaTime = Math.min((currentTime - this.lastTime) / 1000, 0.05); // 限制最大帧间隔
this.lastTime = currentTime;
this.update(deltaTime);
this.render();
requestAnimationFrame(() => this.gameLoop());
}
private update(deltaTime: number): void {
this.gameTime += deltaTime;
// 更新玩家
this.player.update(deltaTime);
// 更新水晶
this.crystals.forEach(crystal => crystal.update(deltaTime));
// 碰撞检测:玩家 vs 墙壁
this.handleWallCollision();
// 碰撞检测:玩家 vs 水晶
this.handleCrystalCollection();
// 更新粒子
this.particles.update(deltaTime);
}
private handleWallCollision(): void {
const playerRect = {
left: this.player.x - this.player.width / 2,
right: this.player.x + this.player.width / 2,
top: this.player.y - this.player.height / 2,
bottom: this.player.y + this.player.height / 2
};
this.walls.forEach(wall => {
if (this.isRectColliding(playerRect, wall)) {
// 简单回弹
const overlapX = Math.min(playerRect.right - wall.x, wall.x + wall.width - playerRect.left);
const overlapY = Math.min(playerRect.bottom - wall.y, wall.y + wall.height - playerRect.top);
if (overlapX < overlapY) {
this.player.x = this.player.x < wall.x + wall.width / 2 ?
wall.x - this.player.width / 2 : wall.x + wall.width + this.player.width / 2;
} else {
this.player.y = this.player.y < wall.y + wall.height / 2 ?
wall.y - this.player.height / 2 : wall.y + wall.height + this.player.height / 2;
}
this.player.velocityX = 0;
this.player.velocityY = 0;
}
});
}
private handleCrystalCollection(): void {
this.crystals.forEach(crystal => {
if (crystal.collected) return;
const dx = this.player.x - crystal.x;
const dy = this.player.y - crystal.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < this.player.lightRadius * 0.3 + 20) {
// 收集水晶
crystal.collected = true;
this.score += 100;
this.collectedColors.add(crystal.color);
// 发射粒子特效
this.particles.emit(crystal.x, crystal.y, crystal.color, 15);
// 同步系统光效(关键:HarmonyOS 6特性)
this.syncSystemLightEffect(crystal.color);
// 临时改变玩家光源颜色
this.player.color = crystal.color;
}
});
}
private syncSystemLightEffect(color: string): void {
// 通知UI层更新光效
if (this.onLightEffectChange) {
this.onLightEffectChange(color, 0.8);
}
// 通过AppStorage同步到悬浮导航组件
AppStorage.setOrCreate('game_light_color', color);
AppStorage.setOrCreate('game_light_pulse', Date.now());
}
private isRectColliding(rect: any, wall: Wall): boolean {
return rect.left < wall.x + wall.width &&
rect.right > wall.x &&
rect.top < wall.y + wall.height &&
rect.bottom > wall.y;
}
private render(): void {
if (!this.canvas) return;
const ctx = this.canvas;
// 清空画布(深色背景增强光效)
ctx.fillStyle = '#0a0a0f';
ctx.fillRect(0, 0, 800, 600);
// 绘制环境雾(未照亮区域更暗)
ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
ctx.fillRect(0, 0, 800, 600);
// 使用globalCompositeOperation实现光照效果
ctx.globalCompositeOperation = 'destination-out';
// 绘制玩家光源照亮区域
const lightGradient = ctx.createRadialGradient(
this.player.x, this.player.y, 0,
this.player.x, this.player.y, this.player.lightRadius
);
lightGradient.addColorStop(0, `rgba(255, 255, 255, ${this.player.lightIntensity})`);
lightGradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
ctx.fillStyle = lightGradient;
ctx.beginPath();
ctx.arc(this.player.x, this.player.y, this.player.lightRadius, 0, Math.PI * 2);
ctx.fill();
// 恢复混合模式
ctx.globalCompositeOperation = 'source-over';
// 绘制墙壁
this.walls.forEach(wall => wall.render(ctx));
// 绘制水晶
this.crystals.forEach(crystal => crystal.render(ctx));
// 绘制粒子
this.particles.render(ctx);
// 绘制玩家
this.player.render(ctx);
}
// 输入控制
setPlayerVelocity(vx: number, vy: number): void {
this.player.velocityX = vx * this.player.speed;
this.player.velocityY = vy * this.player.speed;
}
}
3.3 游戏悬浮控制盘(GameFloatNav.ets)
代码亮点:针对游戏场景优化的悬浮导航组件,采用扇形/环形布局替代传统线性导航,支持手势拖拽定位,确保不遮挡游戏核心画面区域。
typescript
// entry/src/main/ets/components/GameFloatNav.ets
import { window } from '@kit.ArkUI';
// 控制盘配置
interface ControlConfig {
position: 'left' | 'right' | 'bottom'; // 悬浮位置
autoHide: boolean; // 是否自动隐藏
transparency: number; // 透明度
size: number; // 控制盘大小
}
// 方向键状态
interface DirectionState {
up: boolean;
down: boolean;
left: boolean;
right: boolean;
}
@Component
export struct GameFloatNav {
// 回调函数
onDirectionChange?: (dx: number, dy: number) => void;
onActionPress?: (action: string) => void;
@State config: ControlConfig = {
position: 'left',
autoHide: true,
transparency: 0.6,
size: 180
};
@State isExpanded: boolean = false;
@State isDragging: boolean = false;
@State navPosition: Position = { x: 20, y: 300 };
@State activeDirections: DirectionState = {
up: false, down: false, left: false, right: false
};
// 光效状态(与游戏同步)
@State lightColor: string = '#00D2FF';
@State lightIntensity: number = 0.6;
// 自动隐藏计时器
private hideTimer: number = -1;
private touchStartPos: Position = { x: 0, y: 0 };
aboutToAppear(): void {
// 监听游戏光效变化
AppStorage.watch('game_light_color', (color: string) => {
this.lightColor = color;
this.triggerPulse();
});
}
private triggerPulse(): void {
// 光效脉冲动画
this.lightIntensity = 1.0;
setTimeout(() => {
this.lightIntensity = 0.6;
}, 300);
}
private startAutoHide(): void {
if (!this.config.autoHide) return;
clearTimeout(this.hideTimer);
this.hideTimer = setTimeout(() => {
this.isExpanded = false;
}, 3000);
}
private calculateDirection(): void {
let dx = 0, dy = 0;
if (this.activeDirections.up) dy -= 1;
if (this.activeDirections.down) dy += 1;
if (this.activeDirections.left) dx -= 1;
if (this.activeDirections.right) dx += 1;
// 归一化
const length = Math.sqrt(dx * dx + dy * dy);
if (length > 0) {
dx /= length;
dy /= length;
}
this.onDirectionChange?.(dx, dy);
}
build() {
Stack() {
// 背景光效层
Column()
.width(this.config.size)
.height(this.config.size)
.backgroundColor(this.lightColor)
.blur(60)
.opacity(this.lightIntensity * 0.3)
.borderRadius(this.config.size / 2)
.animation({
duration: 300,
curve: Curve.EaseInOut
})
// 主控制盘(玻璃拟态)
Column() {
// 方向控制区(十字键)
this.buildDirectionPad()
// 展开后的功能按钮
if (this.isExpanded) {
this.buildActionButtons()
}
}
.width(this.isExpanded ? this.config.size : 80)
.height(this.isExpanded ? this.config.size + 100 : 80)
.backgroundColor(`rgba(30, 30, 40, ${this.config.transparency})`)
.backdropFilter($r('sys.blur.20'))
.borderRadius(this.isExpanded ? 24 : 40)
.border({
width: 1,
color: `rgba(255, 255, 255, 0.1)`,
style: BorderStyle.Solid
})
.shadow({
radius: 20,
color: 'rgba(0, 0, 0, 0.4)',
offsetX: 0,
offsetY: 4
})
.animation({
duration: 250,
curve: Curve.Spring,
iterations: 1
})
// 拖拽指示器
if (this.isDragging) {
Column()
.width(40)
.height(40)
.backgroundColor('rgba(255, 255, 255, 0.2)')
.borderRadius(20)
.position({ x: -20, y: -20 })
}
}
.width(this.isExpanded ? this.config.size : 80)
.height(this.isExpanded ? this.config.size + 100 : 80)
.position({
x: this.navPosition.x,
y: this.navPosition.y
})
.onTouch((event: TouchEvent) => {
switch (event.type) {
case TouchType.Down:
this.touchStartPos = { x: event.touches[0].x, y: event.touches[0].y };
this.isDragging = true;
break;
case TouchType.Move:
if (this.isDragging) {
this.navPosition = {
x: this.navPosition.x + event.touches[0].x - this.touchStartPos.x,
y: this.navPosition.y + event.touches[0].y - this.touchStartPos.y
};
}
break;
case TouchType.Up:
this.isDragging = false;
this.isExpanded = !this.isExpanded;
this.startAutoHide();
break;
}
})
}
@Builder
buildDirectionPad(): void {
Column({ space: 4 }) {
// 上
this.buildDirectionButton('up', $r('app.media.ic_up'), 0, -1)
Row({ space: 4 }) {
// 左
this.buildDirectionButton('left', $r('app.media.ic_left'), -1, 0)
// 中心(功能按钮)
Button() {
Image($r('app.media.ic_menu'))
.width(24)
.height(24)
.fillColor('#FFFFFF')
}
.type(ButtonType.Circle)
.backgroundColor(this.lightColor)
.width(50)
.height(50)
.shadow({
radius: 10,
color: this.lightColor,
offsetX: 0,
offsetY: 0
})
.onClick(() => {
this.isExpanded = !this.isExpanded;
this.startAutoHide();
})
// 右
this.buildDirectionButton('right', $r('app.media.ic_right'), 1, 0)
}
// 下
this.buildDirectionButton('down', $r('app.media.ic_down'), 0, 1)
}
.padding(8)
}
@Builder
buildDirectionButton(direction: string, icon: Resource, dx: number, dy: number): void {
Button() {
Image(icon)
.width(20)
.height(20)
.fillColor(this.activeDirections[direction as keyof DirectionState] ? this.lightColor : '#B0B0B0')
}
.type(ButtonType.Circle)
.backgroundColor(
this.activeDirections[direction as keyof DirectionState] ?
`rgba(255, 255, 255, 0.15)` : 'rgba(255, 255, 255, 0.05)'
)
.width(44)
.height(44)
.onTouch((event: TouchEvent) => {
if (event.type === TouchType.Down) {
this.activeDirections[direction as keyof DirectionState] = true;
} else if (event.type === TouchType.Up) {
this.activeDirections[direction as keyof DirectionState] = false;
}
this.calculateDirection();
})
}
@Builder
buildActionButtons(): void {
Row({ space: 12 }) {
// 道具按钮
this.buildActionButton('item', $r('app.media.ic_item'), '使用道具')
// 地图按钮
this.buildActionButton('map', $r('app.media.ic_map'), '地图')
// 设置按钮
this.buildActionButton('settings', $r('app.media.ic_settings'), '设置')
}
.margin({ top: 16 })
.padding({ left: 12, right: 12 })
}
@Builder
buildActionButton(action: string, icon: Resource, label: string): void {
Column() {
Button() {
Image(icon)
.width(22)
.height(22)
.fillColor('#FFFFFF')
}
.type(ButtonType.Circle)
.backgroundColor('rgba(255, 255, 255, 0.1)')
.width(48)
.height(48)
.onClick(() => {
this.onActionPress?.(action);
})
Text(label)
.fontSize(10)
.fontColor('rgba(255, 255, 255, 0.6)')
.margin({ top: 4 })
}
}
}
游戏悬浮导航设计要点:
- 环形布局:方向键采用十字布局,中心为菜单按钮,符合游戏手柄直觉
- 拖拽定位:玩家可自由拖动控制盘到任意位置,避免遮挡关键游戏区域
- 光效同步:收集水晶时控制盘背景光效同步变化,提供视觉反馈
- 自动隐藏:非操作状态下自动收缩为圆形按钮,最大化游戏视野
3.4 游戏主页面(GamePage.ets)
typescript
// entry/src/main/ets/pages/GamePage.ets
import { GameEngine } from '../engine/GameEngine';
import { GameFloatNav } from '../components/GameFloatNav';
import { window } from '@kit.ArkUI';
@Entry
@Component
struct GamePage {
private canvasContext: CanvasRenderingContext2D | null = null;
private gameEngine: GameEngine = new GameEngine();
@State score: number = 0;
@State gameTime: string = '00:00';
@State collectedCount: number = 0;
@State totalCrystals: number = 5;
@State showSettings: boolean = false;
@State showMap: boolean = false;
// 屏幕尺寸适配
@State screenWidth: number = 800;
@State screenHeight: number = 600;
aboutToAppear(): void {
// 获取屏幕尺寸
this.getScreenSize();
}
private async getScreenSize(): Promise<void> {
try {
const win = await window.getLastWindow();
const rect = win.getWindowProperties().windowRect;
this.screenWidth = rect.width;
this.screenHeight = rect.height;
} catch (error) {
console.error('Failed to get screen size:', error);
}
}
build() {
Stack({ alignContent: Alignment.Center }) {
// 游戏画布
Canvas(this.canvasContext)
.width(this.screenWidth)
.height(this.screenHeight)
.backgroundColor('#0a0a0f')
.onReady((context: CanvasRenderingContext2D) => {
this.canvasContext = context;
this.gameEngine.initialize(context);
// 绑定光效同步回调
this.gameEngine.onLightEffectChange = (color: string, intensity: number) => {
this.syncSystemLightEffect(color, intensity);
};
this.gameEngine.start();
})
// 顶部HUD(玻璃拟态)
this.buildHUD()
// 悬浮控制盘
GameFloatNav({
onDirectionChange: (dx: number, dy: number) => {
this.gameEngine.setPlayerVelocity(dx, dy);
},
onActionPress: (action: string) => {
this.handleAction(action);
}
})
// 设置面板(沉浸光感)
if (this.showSettings) {
SettingsPanel({
onClose: () => { this.showSettings = false; }
})
}
// 小地图(玻璃拟态)
if (this.showMap) {
MiniMap({
playerX: this.gameEngine.player.x,
playerY: this.gameEngine.player.y,
walls: this.gameEngine.walls,
crystals: this.gameEngine.crystals,
onClose: () => { this.showMap = false; }
})
}
}
.width('100%')
.height('100%')
.backgroundColor('#000000')
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
}
@Builder
buildHUD(): void {
Row() {
// 分数
Column() {
Text(`${this.score}`)
.fontSize(28)
.fontWeight(FontWeight.Bold)
.fontColor('#FFFFFF')
Text('SCORE')
.fontSize(10)
.fontColor('rgba(255,255,255,0.5)')
}
.backgroundColor('rgba(30, 30, 40, 0.6)')
.backdropFilter($r('sys.blur.15'))
.padding({ left: 20, right: 20, top: 12, bottom: 12 })
.borderRadius(16)
// 收集进度
Row({ space: 8 }) {
ForEach([0, 1, 2, 3, 4], (index: number) => {
Column()
.width(24)
.height(24)
.backgroundColor(index < this.collectedCount ? '#4ECDC4' : 'rgba(255,255,255,0.1)')
.borderRadius(12)
.border({
width: 2,
color: index < this.collectedCount ? '#4ECDC4' : 'rgba(255,255,255,0.2)'
})
})
}
.backgroundColor('rgba(30, 30, 40, 0.6)')
.backdropFilter($r('sys.blur.15'))
.padding(12)
.borderRadius(16)
// 时间
Column() {
Text(this.gameTime)
.fontSize(24)
.fontColor('#FFFFFF')
.fontVariant(FontVariant.TabNums)
}
.backgroundColor('rgba(30, 30, 40, 0.6)')
.backdropFilter($r('sys.blur.15'))
.padding({ left: 20, right: 20, top: 12, bottom: 12 })
.borderRadius(16)
}
.width('100%')
.justifyContent(FlexAlign.SpaceEvenly)
.padding({ top: 40, left: 20, right: 20 })
.position({ x: 0, y: 0 })
}
private handleAction(action: string): void {
switch (action) {
case 'settings':
this.showSettings = true;
break;
case 'map':
this.showMap = true;
break;
case 'item':
// 使用道具逻辑
break;
}
}
private syncSystemLightEffect(color: string, intensity: number): void {
// 同步到系统沉浸光效
console.info(`Syncing light effect: ${color} at ${intensity}`);
// 这里可以调用系统级光效API(HarmonyOS 6特性)
// 例如:控制设备LED灯效、通知栏光效等
}
}
// 设置面板组件(玻璃拟态沉浸光感)
@Component
struct SettingsPanel {
onClose: () => void = () => {};
@State musicVolume: number = 80;
@State sfxVolume: number = 100;
@State lightIntensity: number = 0.6;
@State showFPS: boolean = false;
build() {
Stack() {
// 背景遮罩
Column()
.width('100%')
.height('100%')
.backgroundColor('rgba(0, 0, 0, 0.7)')
.onClick(() => this.onClose())
// 设置面板(玻璃拟态)
Column({ space: 20 }) {
Text('游戏设置')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#FFFFFF')
.margin({ bottom: 20 })
// 音乐音量
SettingSlider({
label: '音乐音量',
value: this.musicVolume,
icon: $r('app.media.ic_music')
})
// 音效音量
SettingSlider({
label: '音效音量',
value: this.sfxVolume,
icon: $r('app.media.ic_sound')
})
// 光效强度
SettingSlider({
label: '光效强度',
value: this.lightIntensity * 100,
icon: $r('app.media.ic_light')
})
// FPS显示开关
Row() {
Text('显示FPS')
.fontSize(16)
.fontColor('#FFFFFF')
Toggle({ type: ToggleType.Switch, isOn: this.showFPS })
.selectedColor('#4ECDC4')
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
// 关闭按钮
Button('关闭')
.width('100%')
.height(48)
.backgroundColor('rgba(255, 255, 255, 0.1)')
.fontColor('#FFFFFF')
.borderRadius(12)
.onClick(() => this.onClose())
}
.width(320)
.padding(24)
.backgroundColor('rgba(30, 30, 40, 0.85)')
.backdropFilter($r('sys.blur.30'))
.borderRadius(24)
.border({
width: 1,
color: 'rgba(255, 255, 255, 0.1)'
})
.shadow({
radius: 30,
color: 'rgba(0, 0, 0, 0.5)',
offsetX: 0,
offsetY: 10
})
}
.width('100%')
.height('100%')
}
}
// 设置滑块组件
@Component
struct SettingSlider {
label: string = '';
@State value: number = 50;
icon: Resource = $r('app.media.ic_settings');
build() {
Column({ space: 8 }) {
Row({ space: 12 }) {
Image(this.icon)
.width(20)
.height(20)
.fillColor('#B0B0B0')
Text(this.label)
.fontSize(14)
.fontColor('#FFFFFF')
Text(`${Math.round(this.value)}%`)
.fontSize(14)
.fontColor('#4ECDC4')
.layoutWeight(1)
.textAlign(TextAlign.End)
}
.width('100%')
Slider({
value: this.value,
min: 0,
max: 100,
step: 1,
style: SliderStyle.OutSet
})
.width('100%')
.selectedColor('#4ECDC4')
.blockColor('#FFFFFF')
.trackColor('rgba(255, 255, 255, 0.1)')
}
.width('100%')
}
}
// 小地图组件
@Component
struct MiniMap {
playerX: number = 0;
playerY: number = 0;
walls: Array<{x: number, y: number, width: number, height: number}> = [];
crystals: Array<{x: number, y: number, collected: boolean}> = [];
onClose: () => void = () => {};
build() {
Stack() {
Column()
.width('100%')
.height('100%')
.backgroundColor('rgba(0, 0, 0, 0.7)')
.onClick(() => this.onClose())
Column() {
Text('迷宫地图')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#FFFFFF')
.margin({ bottom: 16 })
// 地图画布
Canvas()
.width(280)
.height(210)
.backgroundColor('rgba(20, 20, 30, 0.8)')
.borderRadius(12)
.onReady((ctx: CanvasRenderingContext2D) => {
const scaleX = 280 / 800;
const scaleY = 210 / 600;
// 绘制墙壁
ctx.fillStyle = 'rgba(255, 255, 255, 0.2)';
this.walls.forEach(wall => {
ctx.fillRect(
wall.x * scaleX,
wall.y * scaleY,
wall.width * scaleX,
wall.height * scaleY
);
});
// 绘制水晶
this.crystals.forEach(crystal => {
if (!crystal.collected) {
ctx.fillStyle = '#4ECDC4';
ctx.beginPath();
ctx.arc(
crystal.x * scaleX,
crystal.y * scaleY,
4, 0, Math.PI * 2
);
ctx.fill();
}
});
// 绘制玩家位置
ctx.fillStyle = '#00D2FF';
ctx.shadowColor = '#00D2FF';
ctx.shadowBlur = 10;
ctx.beginPath();
ctx.arc(
this.playerX * scaleX,
this.playerY * scaleY,
6, 0, Math.PI * 2
);
ctx.fill();
ctx.shadowBlur = 0;
})
Button('关闭')
.width('100%')
.height(40)
.backgroundColor('rgba(255, 255, 255, 0.1)')
.fontColor('#FFFFFF')
.borderRadius(8)
.margin({ top: 16 })
.onClick(() => this.onClose())
}
.width(320)
.padding(20)
.backgroundColor('rgba(30, 30, 40, 0.9)')
.backdropFilter($r('sys.blur.30'))
.borderRadius(20)
}
.width('100%')
.height('100%')
}
}
3.5 游戏光效同步控制器
typescript
// entry/src/main/ets/utils/LightEffectController.ets
import { sensor } from '@kit.SensorServiceKit';
export class LightEffectController {
private static instance: LightEffectController;
private vibrator: sensor.Vibrator | null = null;
// 当前光效状态
currentColor: string = '#00D2FF';
currentIntensity: number = 0.6;
static getInstance(): LightEffectController {
if (!LightEffectController.instance) {
LightEffectController.instance = new LightEffectController();
}
return LightEffectController.instance;
}
async initialize(): Promise<void> {
try {
// 初始化震动反馈
this.vibrator = sensor.vibrator;
console.info('LightEffectController initialized');
} catch (error) {
console.error('Failed to initialize vibrator:', error);
}
}
// 触发收集光效(带震动反馈)
async triggerCollectionEffect(color: string): Promise<void> {
this.currentColor = color;
this.currentIntensity = 1.0;
// 震动反馈
try {
await this.vibrator?.startVibration({
type: 'time',
duration: 100
}, {
id: 0,
usage: 'game'
});
} catch (error) {
console.error('Vibration failed:', error);
}
// 光效衰减
this.fadeLightEffect();
}
private fadeLightEffect(): void {
const fadeInterval = setInterval(() => {
this.currentIntensity -= 0.05;
if (this.currentIntensity <= 0.6) {
this.currentIntensity = 0.6;
clearInterval(fadeInterval);
}
// 同步到系统
this.syncToSystem();
}, 50);
}
private syncToSystem(): void {
// 同步到AppStorage,触发UI更新
AppStorage.setOrCreate('system_light_color', this.currentColor);
AppStorage.setOrCreate('system_light_intensity', this.currentIntensity);
// 这里可以调用HarmonyOS 6系统级光效API
// 例如:控制设备氛围灯、屏幕边缘光效等
}
// 根据游戏时间调整环境光(昼夜循环)
updateAmbientLight(gameTime: number): void {
const cycle = (gameTime % 60) / 60; // 60秒一个周期
const intensity = 0.3 + Math.sin(cycle * Math.PI * 2) * 0.3;
AppStorage.setOrCreate('ambient_light_intensity', intensity);
}
}
四、关键技术总结
4.1 游戏场景下的悬浮导航设计原则
| 设计维度 | 传统移动端游戏 | HarmonyOS 6悬浮导航 |
|---|---|---|
| 位置固定性 | 固定于屏幕角落 | 可拖拽悬浮,自由定位 |
| 视觉遮挡 | opaque背景遮挡画面 | 毛玻璃半透明,保持沉浸 |
| 交互反馈 | 简单点击 | 光效同步+震动反馈 |
| 状态变化 | 静态UI | 动态光效随游戏状态变化 |
| 多任务 | 单窗口全屏 | 支持悬浮窗多任务 |
4.2 沉浸光感在游戏中的创新应用
- 情绪光效同步:玩家生命值低时UI泛红光,收集道具时闪烁对应颜色
- 环境光联动:游戏内昼夜变化同步调整系统屏幕色温
- 社交光效:多人游戏时不同玩家颜色通过光效区分
- 成就光效:解锁成就时触发全屏光效脉冲
4.3 性能优化策略
typescript
// 光效性能优化:离屏渲染缓存
class LightEffectCache {
private cache: Map<string, ImageBitmap> = new Map();
getLightGradient(radius: number, color: string, intensity: number): ImageBitmap {
const key = `${radius}-${color}-${intensity}`;
if (!this.cache.has(key)) {
// 创建离屏画布预渲染光效
const offscreen = new OffscreenCanvas(radius * 2, radius * 2);
const ctx = offscreen.getContext('2d')!;
const gradient = ctx.createRadialGradient(
radius, radius, 0,
radius, radius, radius
);
gradient.addColorStop(0, color);
gradient.addColorStop(1, 'transparent');
ctx.fillStyle = gradient;
ctx.globalAlpha = intensity;
ctx.fillRect(0, 0, radius * 2, radius * 2);
this.cache.set(key, offscreen.transferToImageBitmap());
}
return this.cache.get(key)!;
}
}
五、调试与发布建议
- 真机性能测试:Canvas2D在HarmonyOS 6设备上的性能表现需通过真机验证,特别关注120Hz高刷模式下的帧率稳定性
- 光效适配:不同设备(手机/平板/PC)的光效强度应可配置,避免OLED屏幕烧屏
- 耗电优化:游戏光效+高刷会显著增加耗电,建议提供"省电模式"降低光效复杂度
- 无障碍支持:为视障玩家提供高对比度模式,关闭动态光效
六、结语
HarmonyOS 6的悬浮导航与沉浸光感特性为游戏开发开辟了全新的交互维度。通过"光影迷宫"的实战案例,我们展示了如何:
- 构建不遮挡游戏画面的悬浮控制盘,支持自由拖拽定位
- 实现游戏内光源与系统UI光效的实时同步联动
- 利用玻璃拟态效果打造现代化的游戏UI面板
- 通过P3广色域支持呈现更绚丽的游戏色彩
这些技术不仅提升了游戏的视觉品质,更创造了操作系统与游戏内容深度融合的沉浸式体验。期待HarmonyOS游戏开发者们能够利用这些新特性,创造出更多令人惊叹的交互体验。
转载自:https://blog.csdn.net/u014727709/article/details/160269775
欢迎 👍点赞✍评论⭐收藏,欢迎指正