鸿蒙NEXT开发案例:拼图

zhongcx
发布于 2024-12-1 09:21
浏览
0收藏

鸿蒙NEXT开发案例:拼图-鸿蒙开发者社区

【引言】
在本案例中,我们将展示如何使用鸿蒙NEXT框架开发一个简单的拼图游戏。该游戏允许用户选择一张图片,并将其分割成多个拼图块,用户可以通过点击交换拼图块的位置,最终完成拼图。
【环境准备】
电脑系统:windows 10
开发工具:DevEco Studio NEXT Beta1 Build Version: 5.0.3.806
工程版本:API 12
真机:Mate 60 Pro
语言:ArkTS、ArkUI
【项目结构】
本项目主要由以下几个部分组成:
图片选择:用户可以从设备中选择一张图片。
拼图生成:将选中的图片分割成多个拼图块。
拼图交换:用户可以通过点击拼图块进行交换。
拼图完成检测:检查用户是否完成拼图,并弹出提示。
【关键代码解析】

  1. 导入必要的模块
    我们首先导入了处理文件、图像、用户界面等功能所需的模块。
import { fileIo as fs } from '@kit.CoreFileKit';
import { common } from '@kit.AbilityKit';
import { promptAction } from '@kit.ArkUI';
import { image } from '@kit.ImageKit';
import { photoAccessHelper } from '@kit.MediaLibraryKit';
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  1. 定义拼图块接口
    我们定义了一个 PuzzlePiece 接口,用于描述拼图块的属性,包括像素地图和原始图片中的索引位置。
interface PuzzlePiece {
  pixelMap: image.PixelMap;
  originalIndex: number;
}
  • 1.
  • 2.
  • 3.
  • 4.
  1. 图片选择功能
    在 openPicker 方法中,我们使用 photoAccessHelper 创建一个图片选择器,用户选择图片后,我们将其复制到应用的文件目录中,并更新状态变量。
async openPicker() {
  // 创建图片选择选项
  let PhotoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
  // 设置只选择图片类型
  PhotoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;
  // 设置最大选择数量为1
  PhotoSelectOptions.maxSelectNumber = 1;
  // 执行图片选择操作
  let uris: photoAccessHelper.PhotoSelectResult = await photoPicker.select(PhotoSelectOptions);
  // 处理选择的图片
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  1. 拼图块生成
    在 imgChange 方法中,我们将选中的图片分割成9个拼图块,并将其存储在 puzzlePieces 数组中。
async imgChange() {
  // 创建图片源对象
  const imageSource: image.ImageSource = image.createImageSource(this.selectedImageUrl);
  // 计算每个拼图块的大小
  const pieceSize: image.Size = {
    width: mImageInfo.size.width / 3,
    height: mImageInfo.size.height / 3,
  };
  // 遍历生成拼图块
  for (let row = 0; row < 3; row++) {
    for (let col = 0; col < 3; col++) {
      // 裁剪像素地图
      await mPixelMap.crop(cutRegion);
      // 添加拼图块至数组
      this.puzzlePieces.push(piece);
    }
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  1. 拼图交换逻辑
    在 build 方法中,我们构建了用户界面,并为每个拼图块添加了点击事件处理逻辑,实现拼图块的交换。
.onClick(() => {
  // 处理拼图交换逻辑
  if (this.selectedPiece == -1) {
    this.selectedPiece = index; // 第一次点击选择拼图块
  } else {
    // 交换拼图块
    let temp: PuzzlePiece = this.puzzlePieces[this.selectedPiece];
    this.puzzlePieces[this.selectedPiece] = this.puzzlePieces[index];
    this.puzzlePieces[index] = temp;
    this.selectedPiece = -1;
    // 检查拼图是否完成
  }
});
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

【结论】
通过本案例,我们展示了如何使用鸿蒙NEXT框架开发一个简单的拼图游戏。该项目涵盖了图片选择、拼图生成、拼图交换等基本功能,适合初学者学习和实践鸿蒙NEXT开发。希望本案例能够帮助您更好地理解鸿蒙NEXT的开发流程。
【完整代码】

// 导入必要的模块
import { fileIo as fs } from '@kit.CoreFileKit'; // 文件操作模块
import { common } from '@kit.AbilityKit'; // 应用能力模块
import { promptAction } from '@kit.ArkUI'; // 用户界面提示模块
import { image } from '@kit.ImageKit'; // 图像处理模块
import { photoAccessHelper } from '@kit.MediaLibraryKit'; // 媒体库访问助手模块

// 定义拼图块的接口
interface PuzzlePiece {
  // 拼图块的像素地图
  pixelMap: image.PixelMap;
  // 原始图片中的索引位置
  originalIndex: number;
}

// 使用装饰器定义页面组件
@Entry // 表明这是一个入口组件
@Component // 表明这是一个组件
struct Page30 {
  // 状态变量:选中图片的URI
  @State selectedImageUrl: string = '';
  // 状态变量:原始图片的URI
  @State originalImageUrl: string = '';
  // 状态变量:存储拼图块的数组
  @State puzzlePieces: Array<PuzzlePiece> = [];
  // 状态变量:记录当前选中的拼图块索引
  @State selectedPiece: number = -1;

  // 弹出图片选择器方法
  async openPicker() {
    try {
      // 创建图片选择选项
      let PhotoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
      // 设置只选择图片类型
      PhotoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;
      // 设置最大选择数量为1
      PhotoSelectOptions.maxSelectNumber = 1;
      // 创建图片选择器实例
      let photoPicker = new photoAccessHelper.PhotoViewPicker();
      // 执行图片选择操作
      let uris: photoAccessHelper.PhotoSelectResult = await photoPicker.select(PhotoSelectOptions);
      // 如果没有选择任何图片则返回
      if (!uris || uris.photoUris.length === 0) return;
      // 获取选中图片的第一张URI
      let uri: string = uris.photoUris[0];
      // 打开文件读取流
      let file = fs.openSync(uri, fs.OpenMode.READ_ONLY);
      // 获取当前上下文
      let context = getContext(this) as common.UIAbilityContext;
      // 新建一个保存裁剪后图片的路径
      let newUrl = context.filesDir + '/test' + new Date().getTime() + '.jpg';
      // 复制图片到新的路径
      fs.copyFileSync(file.fd, newUrl);
      // 关闭文件读取流
      fs.closeSync(file);
      // 更新状态变量:设置显示图片的URI
      this.selectedImageUrl = newUrl;
      // 更新状态变量:保存原始图片的URI
      this.originalImageUrl = uri;
      // 图片更改时触发的方法
      this.imgChange();
    } catch (e) {
      // 错误处理
      console.error('openPicker', JSON.stringify(e));
    }
  }

  // 图片更改处理方法
  async imgChange() {
    try {
      // 创建图片源对象
      const imageSource: image.ImageSource = image.createImageSource(this.selectedImageUrl);
      // 图片解码选项
      let decodingOptions: image.DecodingOptions = {
        editable: true, // 允许编辑
        desiredPixelFormat: 3, // 设置期望的像素格式
      };
      // 创建像素地图
      let mPixelMap: image.PixelMap = await imageSource.createPixelMap(decodingOptions);
      // 获取图片信息
      let mImageInfo: image.ImageInfo = await mPixelMap.getImageInfo();
      // 计算每个拼图块的大小
      const pieceSize: image.Size = {
        width: mImageInfo.size.width / 3, // 每个拼图块宽度为图片宽度的1/3
        height: mImageInfo.size.height / 3, // 每个拼图块高度为图片高度的1/3
      };
      // 清空已有拼图块数据
      this.puzzlePieces.splice(0);
      // 遍历图片生成9个拼图块
      let count = 0;
      for (let row = 0; row < 3; row++) {
        for (let col = 0; col < 3; col++) {
          // 创建基于原图的新图片源
          const imageSource = image.createImageSource(this.selectedImageUrl);
          // 创建新像素地图
          let mPixelMap = await imageSource.createPixelMap(decodingOptions);
          // 计算裁剪区域
          const cutRegion: image.Region = {
            x: col * pieceSize.width, // 裁剪起始X坐标
            y: row * pieceSize.height, // 裁剪起始Y坐标
            size: pieceSize, // 裁剪区域大小
          };
          // 裁剪像素地图
          await mPixelMap.crop(cutRegion);
          // 创建并添加拼图块至数组
          const piece: PuzzlePiece = {
            pixelMap: mPixelMap,
            originalIndex: count++, // 记录拼图块在原始图片中的索引
          };
          this.puzzlePieces.push(piece);
        }
      }
      // 打乱拼图块顺序
      for (let i = this.puzzlePieces.length - 1; i > 0; i--) {
        const j = Math.floor(Math.random() * (i + 1)); // 随机选择一个位置
        let temp: PuzzlePiece = this.puzzlePieces[i]; // 临时存储当前拼图块
        this.puzzlePieces[i] = this.puzzlePieces[j]; // 交换位置
        this.puzzlePieces[j] = temp;
      }
    } catch (e) {
      // 错误处理
      console.error('imgChange', JSON.stringify(e));
    }
  }

  // 构建UI界面
  build() {
    Column() { // 创建垂直布局
      // 添加选择图片按钮,点击后调用打开图片选择器方法
      Button('选择图片→').onClick(() => {
        this.openPicker();
      });
      // 显示原始图片(如果已选择)
      if (this.originalImageUrl) {
        Text('原始图片↓'); // 文本提示
        Image(this.originalImageUrl) // 显示原始图片
          .width('180lpx') // 设置图片宽度
          .height('180lpx') // 设置图片高度
          .objectFit(ImageFit.Contain); // 图片适应方式
      }
      // 如果有拼图块,则显示游戏区
      if (this.puzzlePieces.length > 0) {
        Text('游戏图片↓'); // 文本提示
        // 游戏区域采用网格布局
        Grid() {
          // 遍历所有拼图块并创建网格项
          ForEach(this.puzzlePieces, (item: PuzzlePiece, index: number) => {
            GridItem() {
              // 显示拼图块图像
              Image(item.pixelMap)
                .width('200lpx') // 设置图片宽度
                .height('200lpx') // 设置图片高度
                .margin('5lpx') // 设置边距
                  // 根据是否选中调整缩放比例
                .scale(this.selectedPiece == index ? { x: 0.5, y: 0.5 } : { x: 1, y: 1 })
                .animation({})
                  // 添加点击事件处理
                .onClick(() => {
                  // 处理拼图交换逻辑
                  if (this.selectedPiece == -1) {
                    this.selectedPiece = index; // 第一次点击选择拼图块
                  } else if (this.selectedPiece == index) {
                    this.selectedPiece = -1; // 再次点击取消选择
                  } else {
                    let temp: PuzzlePiece = this.puzzlePieces[this.selectedPiece];
                    this.puzzlePieces[this.selectedPiece] = this.puzzlePieces[index];
                    this.puzzlePieces[index] = temp;
                    this.selectedPiece = -1;
                    // 检查拼图是否完成
                    let isSucc: boolean = true;
                    for (let i = 0; i < this.puzzlePieces.length; i++) {
                      if (this.puzzlePieces[i].originalIndex !== i) {
                        isSucc = false;
                        break;
                      }
                    }
                    // 如果拼图完成,弹出提示对话框
                    if (isSucc) {
                      promptAction.showDialog({
                        message: '拼图完成!',
                      });
                    }
                  }
                });
            }
          })
        }
        .backgroundColor("#fafafa"); // 设置网格背景色
      }
    }
    .width('100%'); // 设置列宽度为100%
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.
  • 173.
  • 174.
  • 175.
  • 176.
  • 177.
  • 178.
  • 179.
  • 180.
  • 181.
  • 182.
  • 183.
  • 184.
  • 185.
  • 186.
  • 187.
  • 188.
  • 189.
  • 190.
  • 191.
  • 192.
  • 193.

分类
标签
收藏
回复
举报


回复
    相关推荐