
鸿蒙跨端智能填色游戏开发指南 原创
鸿蒙跨端智能填色游戏开发指南
一、系统架构设计
基于HarmonyOS的AI能力和分布式技术,构建智能填色游戏系统:
图像识别层:识别线稿区域和可填色部分
配色推荐层:根据主题和风格推荐配色方案
游戏逻辑层:管理填色进度和评分
跨端同步层:多设备间同步填色进度和作品
!https://example.com/harmony-coloring-game-arch.png
二、核心代码实现
填色游戏服务
// ColoringService.ets
import ai from ‘@ohos.ai’;
import image from ‘@ohos.multimedia.image’;
import distributedData from ‘@ohos.distributedData’;
import { ColoringImage, ColorPalette, ColoringArea } from ‘./ColoringTypes’;
class ColoringService {
private static instance: ColoringService = null;
private modelManager: ai.ModelManager;
private dataManager: distributedData.DataManager;
private listeners: ColoringListener[] = [];
private constructor() {
this.initModelManager();
this.initDataManager();
public static getInstance(): ColoringService {
if (!ColoringService.instance) {
ColoringService.instance = new ColoringService();
return ColoringService.instance;
private initModelManager(): void {
try {
this.modelManager = ai.createModelManager(getContext());
// 加载线稿识别模型
this.modelManager.loadModel({
modelName: 'line_art_detection',
modelPath: 'resources/rawfile/line_art.model',
callback: (err, data) => {
if (err) {
console.error('加载线稿模型失败:', JSON.stringify(err));
}
});
// 加载配色推荐模型
this.modelManager.loadModel({
modelName: 'color_recommendation',
modelPath: 'resources/rawfile/color_recommend.model',
callback: (err, data) => {
if (err) {
console.error('加载配色模型失败:', JSON.stringify(err));
}
});
catch (err) {
console.error('初始化模型管理器失败:', JSON.stringify(err));
}
private initDataManager(): void {
this.dataManager = distributedData.createDataManager({
bundleName: ‘com.example.coloring’,
area: distributedData.Area.GLOBAL,
isEncrypted: true
});
this.dataManager.registerDataListener('coloring_sync', (data) => {
this.handleSyncData(data);
});
public async requestPermissions(): Promise<boolean> {
try {
const permissions = [
'ohos.permission.USE_AI',
'ohos.permission.READ_MEDIA',
'ohos.permission.DISTRIBUTED_DATASYNC'
];
const result = await abilityAccessCtrl.requestPermissionsFromUser(
getContext(),
permissions
);
return result.grantedPermissions.length === permissions.length;
catch (err) {
console.error('请求权限失败:', JSON.stringify(err));
return false;
}
public async analyzeLineArt(imageData: ArrayBuffer): Promise<ColoringImage> {
try {
const input = {
data: imageData,
width: 512,
height: 512,
format: ‘GRAY’
};
const output = await this.modelManager.runModel({
modelName: 'line_art_detection',
input: input
});
const coloringImage: ColoringImage = {
id: Date.now().toString(),
originalImage: imageData,
areas: output.result.areas.map((area, index) => ({
id: area_${index},
path: area.path,
recommendedColor: area.recommendedColor || '#FFFFFF',
filledColor: null,
isFilled: false
})),
palette: this.generateDefaultPalette(),
createdAt: Date.now()
};
// 生成推荐配色
const recommendedPalette = await this.recommendPalette(imageData);
coloringImage.palette = recommendedPalette;
return coloringImage;
catch (err) {
console.error('线稿分析失败:', JSON.stringify(err));
throw err;
}
private async recommendPalette(imageData: ArrayBuffer): Promise<ColorPalette> {
try {
const input = {
data: imageData,
width: 512,
height: 512,
format: ‘GRAY’
};
const output = await this.modelManager.runModel({
modelName: 'color_recommendation',
input: input
});
return {
id: 'recommended_' + Date.now().toString(),
colors: output.result.colors,
theme: output.result.theme,
style: output.result.style
};
catch (err) {
console.error('配色推荐失败:', JSON.stringify(err));
return this.generateDefaultPalette();
}
private generateDefaultPalette(): ColorPalette {
return {
id: ‘default’,
colors: [
‘#FF5252’, ‘#FF4081’, ‘#E040FB’, ‘#7C4DFF’,
‘#536DFE’, ‘#448AFF’, ‘#40C4FF’, ‘#18FFFF’,
‘#64FFDA’, ‘#69F0AE’, ‘#B2FF59’, ‘#EEFF41’,
‘#FFFF00’, ‘#FFD740’, ‘#FFAB40’, ‘#FF6E40’
],
theme: ‘vibrant’,
style: ‘flat’
};
public async fillArea(imageId: string, areaId: string, color: string): Promise<ColoringImage> {
const image = this.getImageById(imageId);
if (!image) throw new Error('未找到指定图片');
const updatedImage: ColoringImage = {
...image,
areas: image.areas.map(area =>
area.id === areaId ? {
...area,
filledColor: color,
isFilled: true
)
};
this.updateImage(updatedImage);
this.syncFillAction({
imageId,
areaId,
color,
timestamp: Date.now()
});
return updatedImage;
public async changePalette(imageId: string, palette: ColorPalette): Promise<ColoringImage> {
const image = this.getImageById(imageId);
if (!image) throw new Error('未找到指定图片');
const updatedImage: ColoringImage = {
...image,
palette
};
this.updateImage(updatedImage);
this.syncPaletteChange({
imageId,
palette,
timestamp: Date.now()
});
return updatedImage;
public async shareArtwork(imageId: string): Promise<void> {
const image = this.getImageById(imageId);
if (!image) throw new Error('未找到指定作品');
this.dataManager.syncData('artwork_share', {
type: 'artwork_shared',
data: image,
timestamp: Date.now()
});
private getImageById(imageId: string): ColoringImage | undefined {
return this.images.find(img => img.id === imageId);
private updateImage(image: ColoringImage): void {
this.images = this.images.map(img =>
img.id === image.id ? image : img
);
this.notifyImageUpdated(image);
private syncFillAction(action: FillAction): void {
this.dataManager.syncData('fill_action', {
type: 'area_filled',
data: action,
timestamp: Date.now()
});
private syncPaletteChange(change: PaletteChange): void {
this.dataManager.syncData('palette_change', {
type: 'palette_changed',
data: change,
timestamp: Date.now()
});
private handleSyncData(data: any): void {
if (!data) return;
switch (data.type) {
case 'area_filled':
this.handleFillAction(data.data);
break;
case 'palette_changed':
this.handlePaletteChange(data.data);
break;
case 'artwork_shared':
this.handleArtworkShared(data.data);
break;
}
private handleFillAction(action: FillAction): void {
const image = this.getImageById(action.imageId);
if (!image) return;
const updatedImage: ColoringImage = {
...image,
areas: image.areas.map(area =>
area.id === action.areaId ? {
...area,
filledColor: action.color,
isFilled: true
)
};
this.updateImage(updatedImage);
private handlePaletteChange(change: PaletteChange): void {
const image = this.getImageById(change.imageId);
if (!image) return;
const updatedImage: ColoringImage = {
...image,
palette: change.palette
};
this.updateImage(updatedImage);
private handleArtworkShared(artwork: ColoringImage): void {
this.notifyArtworkShared(artwork);
private notifyImageUpdated(image: ColoringImage): void {
this.listeners.forEach(listener => {
listener.onImageUpdated?.(image);
});
private notifyArtworkShared(artwork: ColoringImage): void {
this.listeners.forEach(listener => {
listener.onArtworkShared?.(artwork);
});
public addListener(listener: ColoringListener): void {
if (!this.listeners.includes(listener)) {
this.listeners.push(listener);
}
public removeListener(listener: ColoringListener): void {
this.listeners = this.listeners.filter(l => l !== listener);
}
interface ColoringListener {
onImageUpdated?(image: ColoringImage): void;
onArtworkShared?(artwork: ColoringImage): void;
export const coloringService = ColoringService.getInstance();
主游戏界面
// GameScreen.ets
import { coloringService } from ‘./ColoringService’;
import { ColoringImage, ColorPalette, ColoringArea } from ‘./ColoringTypes’;
@Component
export struct GameScreen {
@State hasPermission: boolean = false;
@State currentImage: ColoringImage | null = null;
@State selectedColor: string = ‘#FF5252’;
@State showColorPicker: boolean = false;
@State showImagePicker: boolean = false;
@State isProcessing: boolean = false;
@State progress: number = 0;
build() {
Stack() {
// 主内容区域
Column() {
// 标题栏
Row() {
Text(‘智能填色’)
.fontSize(24)
.fontWeight(FontWeight.Bold)
.layoutWeight(1)
Button(this.hasPermission ? '选择图片' : '授权')
.width(120)
.onClick(() => {
if (this.hasPermission) {
this.showImagePicker = true;
else {
this.requestPermissions();
})
.padding(10)
.width('100%')
// 进度显示
Row() {
Text('完成进度:')
.fontSize(16)
.margin({ right: 10 })
Progress({
value: this.progress,
total: 100,
style: ProgressStyle.Linear
})
.width(200)
.height(10)
Text(${this.progress}%)
.fontSize(16)
.fontColor('#409EFF')
.margin({ left: 10 })
.padding(10)
.width('100%')
// 画布区域
if (this.currentImage) {
Canvas(this.currentImage)
.width('100%')
.height(400)
.margin({ top: 20, bottom: 20 })
else {
Column() {
Text('请选择一张线稿图片开始填色')
.fontSize(18)
.margin({ bottom: 10 })
Text('点击右上角"选择图片"按钮')
.fontSize(16)
.fontColor('#666666')
.padding(20)
.width('90%')
.backgroundColor('#F5F5F5')
.borderRadius(8)
.margin({ top: 100 })
// 调色板
if (this.currentImage?.palette) {
Column() {
Text('调色板')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 10 })
Grid() {
ForEach(this.currentImage.palette.colors, (color) => {
GridItem() {
Button('')
.width(40)
.height(40)
.backgroundColor(color)
.border({ width: this.selectedColor === color ? 3 : 0, color: '#000000' })
.onClick(() => {
this.selectedColor = color;
})
})
.columnsTemplate(‘1fr 1fr 1fr 1fr’)
.rowsGap(10)
.columnsGap(10)
.margin({ bottom: 10 })
Button('更多颜色')
.width(200)
.onClick(() => {
this.showColorPicker = true;
})
.padding(15)
.width('100%')
.backgroundColor('#FFFFFF')
.borderRadius(8)
}
.width('100%')
.height('100%')
.padding(20)
// 颜色选择器
if (this.showColorPicker) {
ColorPickerDialog({
selectedColor: this.selectedColor,
onColorSelected: (color) => {
this.selectedColor = color;
this.showColorPicker = false;
},
onCancel: () => {
this.showColorPicker = false;
})
// 图片选择器
if (this.showImagePicker) {
ImagePickerDialog({
onImageSelected: (imageData) => {
this.loadImage(imageData);
this.showImagePicker = false;
},
onCancel: () => {
this.showImagePicker = false;
})
// 处理中状态
if (this.isProcessing) {
Column() {
Progress({})
.width(50)
.height(50)
Text('处理中...')
.fontSize(16)
.margin({ top: 10 })
.width(‘100%’)
.height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.backgroundColor('rgba(0,0,0,0.5)')
}
.width('100%')
.height('100%')
.onAppear(() => {
this.checkPermissions();
coloringService.addListener({
onImageUpdated: (image) => {
this.handleImageUpdated(image);
},
onArtworkShared: (artwork) => {
this.handleArtworkShared(artwork);
});
})
.onDisappear(() => {
coloringService.removeListener({
onImageUpdated: (image) => {
this.handleImageUpdated(image);
},
onArtworkShared: (artwork) => {
this.handleArtworkShared(artwork);
});
})
private Canvas(image: ColoringImage): void {
Canvas()
.width('100%')
.height('100%')
.onReady(() => {
const ctx = this.getContext();
// 绘制原始线稿
ctx.drawImage(image.originalImage, 0, 0, ctx.width, ctx.height);
// 绘制已填色区域
image.areas.forEach(area => {
if (area.isFilled && area.filledColor) {
ctx.fillStyle = area.filledColor;
ctx.fillPath(area.path);
});
// 高亮当前选中区域
if (this.hoveredArea) {
ctx.strokeStyle = '#409EFF';
ctx.lineWidth = 3;
ctx.strokePath(this.hoveredArea.path);
})
.onTouch((event: TouchEvent) => {
if (event.type === TouchType.Down && this.currentImage) {
const point = { x: event.touches[0].x, y: event.touches[0].y };
const area = this.findAreaAtPoint(point);
if (area && !area.isFilled) {
coloringService.fillArea(this.currentImage.id, area.id, this.selectedColor);
}
})
private findAreaAtPoint(point: { x: number, y: number }): ColoringArea | undefined {
if (!this.currentImage) return undefined;
return this.currentImage.areas.find(area => {
return this.isPointInPath(point, area.path);
});
private isPointInPath(point: { x: number, y: number }, path: Path2D): boolean {
// 简化的点是否在路径内的检测
// 实际应用中应使用更精确的检测方法
return true;
private updateProgress(): void {
if (!this.currentImage) {
this.progress = 0;
return;
const filledCount = this.currentImage.areas.filter(a => a.isFilled).length;
this.progress = Math.round((filledCount / this.currentImage.areas.length) * 100);
private async checkPermissions(): Promise<void> {
try {
const permissions = [
'ohos.permission.USE_AI',
'ohos.permission.READ_MEDIA',
'ohos.permission.DISTRIBUTED_DATASYNC'
];
const result = await abilityAccessCtrl.verifyPermissions(
getContext(),
permissions
);
this.hasPermission = result.every(perm => perm.granted);
catch (err) {
console.error('检查权限失败:', JSON.stringify(err));
this.hasPermission = false;
}
private async requestPermissions(): Promise<void> {
this.hasPermission = await coloringService.requestPermissions();
if (!this.hasPermission) {
prompt.showToast({ message: '授权失败,无法使用填色功能' });
}
private async loadImage(imageData: ArrayBuffer): Promise<void> {
try {
this.isProcessing = true;
this.currentImage = await coloringService.analyzeLineArt(imageData);
catch (err) {
console.error('加载图片失败:', JSON.stringify(err));
prompt.showToast({ message: '加载图片失败,请重试' });
finally {
this.isProcessing = false;
}
private handleImageUpdated(image: ColoringImage): void {
if (this.currentImage && this.currentImage.id === image.id) {
this.currentImage = image;
this.updateProgress();
}
private handleArtworkShared(artwork: ColoringImage): void {
prompt.showToast({ message: 收到分享作品: ${artwork.id} });
private async shareCurrentArtwork(): Promise<void> {
if (!this.currentImage) return;
try {
await coloringService.shareArtwork(this.currentImage.id);
prompt.showToast({ message: '作品分享成功' });
catch (err) {
console.error('分享作品失败:', JSON.stringify(err));
prompt.showToast({ message: '分享作品失败,请重试' });
}
private async changePalette(palette: ColorPalette): Promise<void> {
if (!this.currentImage) return;
try {
this.currentImage = await coloringService.changePalette(
this.currentImage.id,
palette
);
catch (err) {
console.error('更改调色板失败:', JSON.stringify(err));
prompt.showToast({ message: '更改调色板失败,请重试' });
}
类型定义
// ColoringTypes.ets
export interface ColoringImage {
id: string;
originalImage: ArrayBuffer;
areas: ColoringArea[];
palette: ColorPalette;
createdAt: number;
export interface ColoringArea {
id: string;
path: Path2D;
recommendedColor: string;
filledColor: string | null;
isFilled: boolean;
export interface ColorPalette {
id: string;
colors: string[];
theme: string;
style: string;
export interface FillAction {
imageId: string;
areaId: string;
color: string;
timestamp: number;
export interface PaletteChange {
imageId: string;
palette: ColorPalette;
timestamp: number;
三、项目配置与权限
权限配置
// module.json5
“module”: {
"requestPermissions": [
“name”: “ohos.permission.USE_AI”,
"reason": "使用AI模型识别线稿和推荐配色"
},
“name”: “ohos.permission.READ_MEDIA”,
"reason": "读取线稿图片"
},
“name”: “ohos.permission.DISTRIBUTED_DATASYNC”,
"reason": "同步填色进度和作品"
],
"abilities": [
“name”: “MainAbility”,
"type": "page",
"visible": true
]
}
四、总结与扩展
本智能填色游戏系统实现了以下核心功能:
线稿识别:自动识别可填色区域
智能配色:根据图像内容推荐配色方案
精准填色:支持点选区域填色
进度同步:多设备间同步填色进度
作品分享:分享完成的填色作品
扩展方向:
AR填色:通过AR技术将填色作品投影到现实世界
动态效果:为填色作品添加动画效果
社区画廊:建立用户作品分享社区
教育模式:结合填色教授色彩理论
3D填色:扩展支持3D模型填色
AI生成线稿:根据用户描述生成自定义线稿
通过HarmonyOS的分布式技术,我们构建了一个富有创意和教育意义的填色游戏,让用户可以在多设备间无缝切换和分享创作过程,同时享受AI技术带来的智能配色体验。
