鸿蒙HarmonyOS 5 小游戏实践:数字华容道(附:源代码)

2025-06-29 09:53:09
110次阅读
0个评论

screenshots (9).gif

数字拼图作为经典的益智游戏,其在鸿蒙OS平台上的实现不仅展现了声明式UI的开发优势,更通过丰富的交互设计和性能优化为用户带来沉浸式体验。本文将深入解析这款数字拼图游戏的技术实现,从数据模型设计、核心算法优化到多维度交互体验构建,为开发者呈现一个完整的鸿蒙应用开发案例。

游戏架构与数据模型设计

鸿蒙数字拼图游戏采用了清晰的MVC架构思想,将游戏逻辑与UI展示分离,通过状态管理实现数据与视图的自动同步。核心数据模型围绕游戏面板构建,支持动态难度调整和状态追踪。

动态游戏状态管理

游戏状态通过@State装饰器实现响应式管理,当数据变化时UI会自动更新,这是鸿蒙声明式UI的核心特性:

@Entry
@Component
struct NumberPuzzle {
  @State gameDifficulty: string = 'easy'; // 难度级别
  @State gridSize: number = 3; // 网格大小
  @State gameBoard: number[] = []; // 游戏面板数据
  @State isGameOver: boolean = false; // 游戏结束状态
  @State moveCount: number = 0; // 移动步数
  @State bestTime: number = Infinity; // 最佳完成时间
  // 其他状态定义...
}

这种状态管理方式避免了传统开发中手动更新UI的繁琐流程,例如当gameBoard数组中的数字位置变化时,界面会自动重新渲染拼图方块,大大提升了开发效率。

三维度难度系统设计

游戏实现了基于网格大小的三级难度体系,通过gridSize状态动态调整游戏复杂度:

private initGame() {
  switch (this.gameDifficulty) {
    case 'easy':
      this.gridSize = 3;
      this.cellSize = 100;
      break;
    case 'medium':
      this.gridSize = 4;
      this.cellSize = 80;
      break;
    case 'hard':
      this.gridSize = 5;
      this.cellSize = 60;
      break;
  }
  // 初始化游戏面板...
}

简单模式(3x3)适合新手,包含8个数字块;中等模式(4x4)扩展到15个数字块;困难模式(5x5)则有24个数字块,随着网格增大,游戏的复杂度呈指数级增长,为不同水平的玩家提供了挑战空间。

响应式UI布局

游戏界面采用响应式设计,根据网格大小自动调整方块尺寸和布局,确保在不同设备上都有良好的显示效果:

Flex({ wrap: FlexWrap.Wrap, direction: FlexDirection.Row }) {
  ForEach(this.gameBoard, (item: number, index: number) => {
    this.renderTile(item, index);
  })
}
.width(`${(this.cellSize + this.cellMargin * 2) * this.gridSize}lpx`)

这种动态宽度计算方式使得游戏能够自适应手机、平板等不同设备的屏幕尺寸,无需为每种设备单独开发界面,体现了鸿蒙OS一次开发多端部署的优势。

核心算法与游戏逻辑实现

数字拼图的核心在于可解性判断、高效乱序和移动逻辑,这些算法的优化直接影响游戏的公平性和流畅度。

可解性判断算法

游戏使用逆序数算法确保生成的拼图状态可解,这是数字拼图的核心算法之一:

private isSolvable(board: number[]): boolean {
  const size = this.gridSize;
  let inversions = 0;
  const blankIndex = board.indexOf(0);
  const blankRow = Math.floor(blankIndex / size); // 空白格所在行
  
  // 计算逆序数
  for (let i = 0; i < board.length - 1; i++) {
    if (board[i] === 0) continue;
    for (let j = i + 1; j < board.length; j++) {
      if (board[j] === 0) continue;
      if (board[i] > board[j]) {
        inversions++;
      }
    }
  }
  
  // 奇偶阶魔方可解性判断
  if (size % 2 === 1) {
    return inversions % 2 === 0;
  } else {
    return (inversions + blankRow) % 2 === 0;
  }
}

该算法通过计算数字序列的逆序数来判断拼图是否可解:对于奇数阶魔方,逆序数必须为偶数;对于偶数阶魔方,逆序数与空白格所在行的和必须为偶数。这种判断确保了游戏生成的每个状态都有解,避免了玩家遇到无解状态的挫败感。

Fisher-Yates高效乱序算法

游戏使用Fisher-Yates算法进行拼图打乱,这是一种时间复杂度为O(n)的高效洗牌算法:

private shuffleGameBoard() {
  let tempBoard = [...this.gameBoard];
  const len = tempBoard.length;
  
  // Fisher-Yates洗牌
  for (let i = len - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [tempBoard[i], tempBoard[j]] = [tempBoard[j], tempBoard[i]];
  }
  
  // 确保可解
  if (!this.isSolvable(tempBoard)) {
    this.shuffleGameBoard();
    return;
  }
  
  this.gameBoard = tempBoard;
}

与简单的随机交换不同,Fisher-Yates算法确保每个位置的数字被交换到任何位置的概率均等,生成真正随机的乱序状态。结合可解性判断,保证了每次游戏的公平性和可玩性。

智能移动逻辑

方块移动逻辑同时支持点击和滑动两种交互方式,通过canMove方法判断移动合法性:

private canMove(tileIndex: number): boolean {
  const blankIndex = this.gameBoard.indexOf(0);
  const blankRow = Math.floor(blankIndex / this.gridSize);
  const blankCol = blankIndex % this.gridSize;
  const tileRow = Math.floor(tileIndex / this.gridSize);
  const tileCol = tileIndex % this.gridSize;
  
  // 检查是否与空白格相邻(上下或左右)
  return (tileRow === blankRow && Math.abs(tileCol - blankCol) === 1) ||
         (tileCol === blankCol && Math.abs(tileRow - blankRow) === 1);
}

移动逻辑不仅判断位置合法性,还通过动画效果增强用户体验,当方块移动时,系统会计算移动方向和距离,生成平滑的过渡动画:

private updateAnim(index: number) {
  if (!this.shouldAnimate) return undefined;
  const blankIndex = this.gameBoard.indexOf(0);
  const diff = Math.abs(index - blankIndex);
  
  // 左右移动动画
  if (diff === 1) {
    return TransitionEffect.translate({
      x: `${(index > blankIndex ? -1 : 1) * (this.cellSize + this.cellMargin * 2)}lpx`
    }).animation({ duration: 150 });
  }
  // 上下移动动画
  else if (diff === this.gridSize) {
    return TransitionEffect.translate({
      y: `${(index > blankIndex ? -1 : 1) * (this.cellSize + this.cellMargin * 2)}lpx`
    }).animation({ duration: 150 });
  }
}

交互设计与沉浸式体验构建

鸿蒙数字拼图在交互设计上融合了多种手势操作和视觉反馈,构建了沉浸式的游戏体验。

多模态交互系统

游戏支持点击和滑动两种核心交互方式,满足不同用户的操作习惯:

// 点击交互
.onClick(() => {
  this.selectTile(index);
  if (this.canMove(index)) {
    this.moveTile(index);
  }
})

// 滑动交互
.onTouch((e) => {
  if (e.type === TouchType.Down) {
    this.screenStartX = e.touches[0].x;
    this.screenStartY = e.touches[0].y;
  } else if (e.type === TouchType.Up) {
    const dx = e.changedTouches[0].x - this.screenStartX;
    const dy = e.changedTouches[0].y - this.screenStartY;
    const distance = Math.sqrt(dx * dx + dy * dy);
    
    if (distance > this.minSwipeDistance) {
      this.handleSwipe(dx, dy);
    }
  }
})

滑动交互中加入了距离判断(minSwipeDistance),避免误触导致的意外移动。系统会根据滑动的方向和距离自动计算移动方向,实现直观的手势操作。

视觉反馈与动效设计

游戏通过颜色变化、动画过渡和阴影效果提供清晰的视觉反馈:

@Builder
private renderTile(item: number, index: number) {
  Text(`${item}`)
    .backgroundColor(this.isTileSelected[index] 
      ? '#FF7043' // 选中时深橙色
      : Color.Orange)
    .fontColor(Color.White)
    .borderRadius(10)
    .shadow({ color: '#00000040', radius: 5 })
    .transition(this.updateAnim(index))
}

选中方块时背景色变为深橙色,未选中时为橙色,通过颜色差异提供即时反馈;圆角和阴影效果增强了界面的立体感;移动动画则通过TransitionEffect实现平滑过渡,提升了操作的流畅感。

游戏状态可视化

游戏将关键状态信息直观地展示在界面上,包括难度级别、移动步数和最佳成绩:

Row() {
  Text(`难度: ${this.getDifficultyText()}`)
    .fontSize(18)
    .fontWeight(600);
  Text(`步数: ${this.moveCount}`)
    .fontSize(18)
    .fontWeight(600)
    .margin({ left: 25 })
  if (this.bestTime !== Infinity) {
    Text(`最佳: ${(this.bestTime / 1000).toFixed(3)}秒`)
      .fontSize(18)
      .fontWeight(600)
      .margin({ left: 25 });
  }
}

当玩家完成游戏时,系统会记录最短用时,并在界面上展示,激励玩家挑战自我。游戏结束时的对话框会显示详细的游戏统计数据,包括用时、步数和最佳成绩:

promptAction.showDialog({
  title: '游戏胜利!',
  message: `恭喜你完成游戏!\n用时:${formattedTime}秒\n最佳成绩:${(this.bestTime / 1000).toFixed(3)}秒\n步数:${this.moveCount}步`,
  buttons: [{ text: '重新开始', color: '#ffa500' }]
})

鸿蒙特性应用与性能优化

游戏开发中充分利用了鸿蒙OS的特有能力,从状态管理到动画效果,再到性能优化,展现了鸿蒙平台的技术优势。

声明式UI与状态驱动开发

鸿蒙的声明式UI模型使得游戏界面与数据状态紧密绑定,当gameBoard或其他状态变化时,UI会自动更新:

// 数据变化自动触发UI更新
this.gameBoard[tileIndex] = this.gameBoard[blankIndex];
this.gameBoard[blankIndex] = temp;

// 视图自动响应状态变化
Text(`${item}`)
  .backgroundColor(this.isTileSelected[index] ? '#FF7043' : Color.Orange)

这种数据驱动视图的模式避免了传统命令式UI中繁琐的DOM操作,开发人员只需关注数据状态的变化,UI会自动同步,大大提高了代码的可维护性。

高效动画系统

鸿蒙的动画系统支持平滑的过渡效果,游戏中实现了方块移动的平移动画:

TransitionEffect.translate({
  x: `${(index > blankIndex ? -1 : 1) * (this.cellSize + this.cellMargin * 2)}lpx`
}).animation({ duration: 150 })

通过TransitionEffectanimation参数可以精确控制动画的类型、距离和持续时间,实现流畅的视觉效果。动画延迟(animationDelay)参数用于协调多个方块的移动顺序,避免动画混乱。

性能优化策略

针对大网格(如5x5)场景,游戏实现了多项性能优化措施:

  1. 虚拟渲染:虽然代码中未显式实现,但鸿蒙的LazyForEach组件支持按需渲染,避免一次性创建大量组件
  2. 算法优化:可解性判断和乱序算法的时间复杂度均为O(n),确保初始化性能
  3. 事件节流:滑动手势中加入距离判断,减少无效事件触发
  4. 状态缓存:避免重复计算已缓存的状态,如网格大小、方块尺寸等
// 虚拟渲染示例(代码中通过useVirtualRendering状态控制)
if (this.useVirtualRendering && this.gridSize > 3) {
  LazyForEach(this.gameBoard, (item: number, index: number) => {
    this.renderTile(item, index);
  })
} else {
  ForEach(this.gameBoard, (item: number, index: number) => {
    this.renderTile(item, index);
  })
}

游戏扩展与进阶方向

基于当前实现,数字拼图游戏还可以从以下几个方面进行扩展和优化:

主题系统与视觉扩展

添加主题切换功能,支持亮色、暗色和自定义主题,通过定义不同的颜色方案实现:

interface Theme {
  tileColor: ResourceColor;
  selectedColor: ResourceColor;
  backgroundColor: ResourceColor;
  textColor: ResourceColor;
}

const lightTheme: Theme = {
  tileColor: Color.Orange,
  selectedColor: '#FF7043',
  backgroundColor: Color.White,
  textColor: Color.White
};

const darkTheme: Theme = {
  tileColor: '#43A047',
  selectedColor: '#2E7D32',
  backgroundColor: '#121212',
  textColor: Color.White
};

社交与排行榜功能

集成鸿蒙的分布式能力,添加在线排行榜和社交分享功能:

// 排行榜数据结构
interface LeaderboardItem {
  userId: string;
  difficulty: string;
  time: number;
  moves: number;
  date: Date;
}

// 提交成绩到排行榜
private submitToLeaderboard() {
  const item: LeaderboardItem = {
    userId: 'user123', // 实际应用中应使用用户ID
    difficulty: this.gameDifficulty,
    time: Date.now() - this.startTime,
    moves: this.moveCount,
    date: new Date()
  };
  
  // 调用鸿蒙分布式能力提交成绩
  distributedDataKit.save('puzzle-leaderboard', item);
}

关卡系统与挑战模式

实现基于关卡的游戏模式,每个关卡有特定的拼图图案或目标:

// 关卡数据结构
interface Level {
  id: number;
  name: string;
  targetBoard: number[]; // 目标拼图状态
  timeLimit: number;    // 时间限制(秒)
  moveLimit: number;    // 步数限制
}

// 关卡列表
const levels: Level[] = [
  {
    id: 1,
    name: "初级挑战",
    targetBoard: [1,2,3,4,5,6,7,8,0],
    timeLimit: 60,
    moveLimit: 30
  },
  {
    id: 2,
    name: "中级挑战",
    targetBoard: [2,1,3,4,5,6,7,8,0], // 特殊目标状态
    timeLimit: 120,
    moveLimit: 50
  }
];

附:代码

import { promptAction } from '@kit.ArkUI';


@Entry
@Component
struct NumberPuzzle {
  @State gameDifficulty: string = 'easy'; // 难度级别
  @State gridSize: number = 3; // 网格大小
  @State gameBoard: number[] = []; // 游戏面板数据,0表示空白格
  @State selectedTile: number = -1; // 当前选中的方块
  @State isTileSelected: boolean[] = []; // 方块选中状态
  @State isGameOver: boolean = false; // 游戏是否结束
  @State cellSize: number = 80; // 单元格大小
  @State cellMargin: number = 5; // 单元格边距
  @State startTime: number = 0; // 游戏开始时间
  @State moveCount: number = 0; // 移动步数
  @State isGameRunning: boolean = false; // 游戏运行状态
  @State screenStartX: number = 0; // 触摸开始时的屏幕X坐标
  @State screenStartY: number = 0; // 触摸开始时的屏幕Y坐标
  @State lastScreenX: number = 0; // 触摸结束时的屏幕X坐标
  @State lastScreenY: number = 0; // 触摸结束时的屏幕Y坐标
  @State shouldAnimate: boolean = true; // 控制动画是否开启
  @State animationDelay: number = 50; // 动画延迟时间
  @State minSwipeDistance: number = 30; // 最小滑动距离
  @State bestTime: number = Infinity; // 最佳完成时间

  aboutToAppear(): void {
    this.initGame();
    this.isGameRunning = true;
    this.startTime = Date.now();
  }

  // 初始化游戏
  private initGame() {
    // 根据难度设置网格大小和数字范围
    switch (this.gameDifficulty) {
      case 'easy':
        this.gridSize = 3;
        this.cellSize = 100;
        break;
      case 'medium':
        this.gridSize = 4;
        this.cellSize = 80;
        break;
      case 'hard':
        this.gridSize = 5;
        this.cellSize = 60;
        break;
    }

    // 初始化游戏面板
    this.gameBoard = [];
    for (let i = 1; i <= this.gridSize * this.gridSize - 1; i++) {
      this.gameBoard.push(i);
    }
    this.gameBoard.push(0); // 空白格

    // 初始化选中状态数组
    this.isTileSelected = Array(this.gridSize * this.gridSize).fill(false);

    this.shuffleGameBoard();
    this.moveCount = 0;
    this.isGameOver = false;
  }

  // 检查指定位置的方块是否可以移动到空白处
  private canMove(tileIndex: number): boolean {
    const blankIndex = this.gameBoard.indexOf(0);
    const blankRow = Math.floor(blankIndex / this.gridSize);
    const blankCol = blankIndex % this.gridSize;
    const tileRow = Math.floor(tileIndex / this.gridSize);
    const tileCol = tileIndex % this.gridSize;

    return (
      (tileRow === blankRow && Math.abs(tileCol - blankCol) === 1) ||
      (tileCol === blankCol && Math.abs(tileRow - blankRow) === 1)
    );
  }

  // 移动方块
  private moveTile(tileIndex: number) {
    if (this.canMove(tileIndex)) {
      const blankIndex = this.gameBoard.indexOf(0);
      let temp = this.gameBoard[tileIndex];
      this.gameBoard[tileIndex] = this.gameBoard[blankIndex];
      this.gameBoard[blankIndex] = temp;
      this.selectedTile = -1;
      this.resetSelectedTiles();
      this.moveCount++; // 增加步数
      this.checkForWin(); // 检查是否获胜
    }
  }

  // 重置所有选中状态
  private resetSelectedTiles() {
    this.isTileSelected = Array(this.gridSize * this.gridSize).fill(false);
  }

  // 选中方块处理
  private selectTile(index: number) {
    this.resetSelectedTiles();
    if (this.canMove(index)) {
      this.isTileSelected[index] = true;
      this.selectedTile = index;
    }
  }

  // 检查是否获胜
  private checkForWin() {
    // 生成目标状态数组
    const winState:Array<number> = [];
    for (let i = 1; i <= this.gridSize * this.gridSize - 1; i++) {
      winState.push(i);
    }
    winState.push(0);

    // 检查当前状态是否与目标状态相同
    this.isGameOver = this.gameBoard.join(',') === winState.join(',');
    if (this.isGameOver) {
      this.isGameRunning = false;
      const elapsedTime = Date.now() - this.startTime;
      const formattedTime = (elapsedTime / 1000).toFixed(3);

      // 更新最佳时间
      if (elapsedTime < this.bestTime) {
        this.bestTime = elapsedTime;
      }

      promptAction.showDialog({
        title: '游戏胜利!',
        message: `恭喜你完成游戏!\n用时:${formattedTime}秒\n最佳成绩:${(this.bestTime / 1000).toFixed(3)}秒\n步数:${this.moveCount}步`,
        buttons: [{ text: '重新开始', color: '#ffa500' }]
      }).then(() => {
        this.initGame();
        this.isGameRunning = true;
        this.startTime = Date.now();
      });
    }
  }

  // 打乱游戏面板(确保可解)
  private shuffleGameBoard() {
    this.startTime = Date.now();
    this.shouldAnimate = false;

    // 复制当前面板
    let tempBoard = [...this.gameBoard];
    const len = tempBoard.length;

    // Fisher-Yates洗牌算法
    for (let i = len - 1; i > 0; i--) {
      const j = Math.floor(Math.random() * (i + 1));
      const temp = tempBoard[i];
      tempBoard[i] = tempBoard[j];
      tempBoard[j] = temp;
    }

    // 确保谜题可解
    if (!this.isSolvable(tempBoard)) {
      this.shuffleGameBoard(); // 递归重新洗牌直到可解
      return;
    }

    this.gameBoard = tempBoard;
    this.selectedTile = -1;
    this.resetSelectedTiles();
    this.shouldAnimate = true;
  }

  // 检查谜题是否可解
  private isSolvable(board: number[]): boolean {
    const size = this.gridSize;
    let inversions = 0;
    const blankIndex = board.indexOf(0);
    const blankRow = Math.floor(blankIndex / size); // 空白格所在行(从下往上数)

    // 计算逆序数
    for (let i = 0; i < board.length - 1; i++) {
      if (board[i] === 0) continue;
      for (let j = i + 1; j < board.length; j++) {
        if (board[j] === 0) continue;
        if (board[i] > board[j]) {
          inversions++;
        }
      }
    }

    // 对于奇数阶魔方,逆序数必须为偶数
    // 对于偶数阶魔方,逆序数与空白格所在行的和必须为偶数
    if (size % 2 === 1) {
      return inversions % 2 === 0;
    } else {
      return (inversions + blankRow) % 2 === 0;
    }
  }

  // 更新动画效果
  private updateAnim(index: number) {
    if (!this.shouldAnimate) return undefined;
    if (this.canMove(index)) {
      const blankIndex = this.gameBoard.indexOf(0);
      const diff = Math.abs(index - blankIndex);

      // 定义移动动画
      if (diff === 1) { // 左右移动
        return TransitionEffect.translate({
          x: `${(index > blankIndex ? -1 : 1) * (this.cellSize + this.cellMargin * 2)}lpx`
        }).animation({ duration: 150, delay: this.animationDelay });
      } else if (diff === this.gridSize) { // 上下移动
        return TransitionEffect.translate({
          y: `${(index > blankIndex ? -1 : 1) * (this.cellSize + this.cellMargin * 2)}lpx`
        }).animation({ duration: 150, delay: this.animationDelay });
      }
    }
    return undefined;
  }

  // 获取已用时间
  private getElapsedTime(): string {
    if (!this.isGameRunning) {
      return (this.moveCount === 0 ? '0.000' : ((Date.now() - this.startTime) / 1000).toFixed(3));
    }
    else {
      return ((Date.now() - this.startTime) / 1000).toFixed(3);
    }
  }

  // 处理滑动手势
  private handleSwipe(dx: number, dy: number) {
    const blankIndex = this.gameBoard.indexOf(0);

    if (Math.abs(dx) > Math.abs(dy)) {
      // 水平滑动
      if (dx > 0) { // 右滑
        if (blankIndex % this.gridSize > 0) {
          this.moveTile(blankIndex - 1);
        }
      } else { // 左滑
        if (blankIndex % this.gridSize < this.gridSize - 1) {
          this.moveTile(blankIndex + 1);
        }
      }
    } else {
      // 垂直滑动
      if (dy > 0) { // 下滑
        if (blankIndex >= this.gridSize) {
          this.moveTile(blankIndex - this.gridSize);
        }
      } else { // 上滑
        if (blankIndex < this.gridSize * (this.gridSize - 1)) {
          this.moveTile(blankIndex + this.gridSize);
        }
      }
    }
  }

  build() {
    Column({ space: 15 }) {
      // 标题栏
      Text('数字拼图游戏')
        .fontSize(28)
        .fontWeight(FontWeight.Bold)
        .margin({ top: 15 })
        .width('100%')
        .textAlign(TextAlign.Center);

      // 状态显示栏
      Row() {
        Text(`难度: ${this.getDifficultyText()}`)
          .fontSize(18)
          .fontWeight(600);
        Text(`步数: ${this.moveCount}`)
          .fontSize(18)
          .fontWeight(600)
          .margin({ left: 25 })
        if (this.bestTime !== Infinity) {
          Text(`最佳: ${(this.bestTime / 1000).toFixed(3)}秒`)
            .fontSize(18)
            .fontWeight(600)
            .margin({ left: 25 });
        }
      }
      .width('100%')
      .justifyContent(FlexAlign.Center);

      // 难度选择按钮
      Row() {
        Button('简单')
          .width('30%')
          .backgroundColor('#FFA726')
          .onClick(() => {
            if (this.gameDifficulty !== 'easy') {
              this.gameDifficulty = 'easy';
              this.initGame();
            }
          });
        Button('中等')
          .width('30%')
          .backgroundColor('#FFA726')
          .onClick(() => {
            if (this.gameDifficulty !== 'medium') {
              this.gameDifficulty = 'medium';
              this.initGame();
            }
          });
        Button('困难')
          .width('30%')
          .backgroundColor('#FFA726')
          .onClick(() => {
            if (this.gameDifficulty !== 'hard') {
              this.gameDifficulty = 'hard';
              this.initGame();
            }
          });
      }
      .width('100%')
      .justifyContent(FlexAlign.SpaceAround)
      .margin({ top: 10 });

      // 游戏面板容器
      Flex({ wrap: FlexWrap.Wrap, direction: FlexDirection.Row }) {
        ForEach(this.gameBoard, (item: number, index: number) => {
          this.renderTile(item, index);
        }, (item: number, index: number) => `${item}-${index}`);
      }
      .width(`${(this.cellSize + this.cellMargin * 2) * this.gridSize}lpx`)
      .margin({ top: 15 })

      // 重新开始按钮
      Button('重新开始')
        .width('60%')
        .height('10%')
        .backgroundColor('#E57373')
        .fontColor(Color.White)
        .margin({ top: 20 })
        .onClick(() => {
          this.initGame();
          this.isGameRunning = true;
          this.startTime = Date.now();
        });
    }
    .width('100%')
    .height('100%')
    .backgroundImage($r('app.media.background'))
    .backgroundImageSize({ width: '100%', height: '100%' })
    .onTouch((e) => {
      if (e.type === TouchType.Down && e.touches.length > 0) {
        this.screenStartX = e.touches[0].x;
        this.screenStartY = e.touches[0].y;
      } else if (e.type === TouchType.Up && e.changedTouches.length > 0) {
        this.lastScreenX = e.changedTouches[0].x;
        this.lastScreenY = e.changedTouches[0].y;

        // 计算滑动距离
        const dx = this.lastScreenX - this.screenStartX;
        const dy = this.lastScreenY - this.screenStartY;
        const distance = Math.sqrt(dx * dx + dy * dy);

        if (distance > this.minSwipeDistance) {
          this.handleSwipe(dx, dy);
        }

        // 重置坐标
        this.screenStartX = 0;
        this.screenStartY = 0;
      }
    })
    .gesture(
      SwipeGesture({ direction: SwipeDirection.All })
        .onAction((_event: GestureEvent) => {
          // 此处保留原有的SwipeGesture,与onTouch结合使用
        })
    );
  }

  // 获取难度文本
  private getDifficultyText(): string {
    switch (this.gameDifficulty) {
      case 'easy': return '简单(3x3)';
      case 'medium': return '中等(4x4)';
      case 'hard': return '困难(5x5)';
      default: return '简单(3x3)';
    }
  }

  // 渲染单个方块
  @Builder
  private renderTile(item: number, index: number) {
    Text(`${item}`)
      .width(`${this.cellSize}lpx`)
      .height(`${this.cellSize}lpx`)
      .margin(`${this.cellMargin}lpx`)
      .fontSize(`${this.cellSize / 2}lpx`)
      .textAlign(TextAlign.Center)
      .backgroundColor(this.isTileSelected[index]
        ? '#FF7043' // 选中时深橙色
        : (this.gameBoard[index] === 0 ? Color.Transparent : Color.Orange))
      .fontColor(Color.White)
      .borderRadius(10)
      .shadow({ color: '#00000040', radius: 5 }) // 添加阴影效果
      .visibility(item == 0 ? Visibility.Hidden : Visibility.Visible)
      .transition(this.updateAnim(index))
      .onClick(() => {
        this.selectTile(index);
        if (this.canMove(index)) {
          this.moveTile(index);
        }
      })
  }
}

结语

鸿蒙OS数字拼图游戏的开发实践展示了平台在应用开发中的综合能力,从声明式UI的高效开发到核心算法的优化实现,再到多模态交互体验的构建,鸿蒙OS为开发者提供了完整的解决方案。这款游戏不仅实现了数字拼图的基本功能,更通过可解性判断、动态难度调整和沉浸式交互设计,为用户带来了优质的游戏体验。

随着鸿蒙生态的不断发展,基于该游戏的扩展空间非常广阔,从主题系统到社交排行榜,再到关卡挑战模式,都可以进一步提升游戏的可玩性和用户粘性。对于开发者而言,数字拼图的实现过程涵盖了状态管理、算法设计、交互优化等多个维度,是理解鸿蒙应用开发的绝佳案例。通过这款游戏,我们可以看到鸿蒙OS在移动应用开发中所具备的强大潜力和广阔前景。

收藏00

登录 后评论。没有帐号? 注册 一个。