OpenHarmony 变色方块游戏开发指南:从设计到实现

文档说明

本文将详细介绍一款基于 OpenHarmony 系统的 "变色方块" 小游戏的开发全过程,从项目设计到具体实现,涵盖核心功能模块、代码结构及关键技术点。本文档面向 OpenHarmony 初学者,采用通俗易懂的语言,结合实际代码片段讲解,帮助读者快速掌握小游戏开发的基本思路和实现方法。

项目概述

项目背景

随着 OpenHarmony 生态的不断发展,越来越多的开发者开始关注基于该系统的应用开发。本项目作为一款入门级小游戏,旨在帮助初学者了解 OpenHarmony 应用开发的基本流程、UI 组件使用及状态管理方法。

游戏简介

"变色方块" 是一款益智类小游戏,核心玩法为:

  • 玩家通过点击网格中的方块改变其颜色
  • 每次点击会同时翻转当前方块及上下左右四个相邻方块的颜色
  • 目标是将所有方块变为黄色以通关
  • 随着关卡提升,网格尺寸逐渐增大(关卡数 + 2 的尺寸)
  • 系统会记录玩家的通关步数,提升游戏挑战性
  • 开发环境

  • 开发工具:DevEco Studio
  • 开发语言:TypeScript
  • 系统版本:OpenHarmony
  • 测试框架:Hypium
  • 模拟框架:Hamock
  • 项目详细介绍

    1. 项目结构设计

    一个清晰的项目结构是代码可维护性的基础,本项目采用如下结构:

    .gitignore 配置说明

    为了避免将不必要的文件提交到代码仓库,我们配置了.gitignore 文件:

    2. 核心功能模块实现

    2.1 游戏核心逻辑模块

    该模块负责实现游戏的核心玩法,包括网格初始化、颜色翻转、通关判定等功能。

  • 网格初始化:根据当前关卡生成对应尺寸的方块网格
  • 颜色翻转:点击方块时翻转自身及相邻方块颜色
  • 通关判定:检测所有方块是否均为黄色
  • 关卡进阶:通关后自动解锁新关卡并重置游戏状态
  • // LevelUtil.ts
    export enum Color {
      YELLOW = 0,    // 目标颜色
      BLUE = 1,      // 其他颜色
      RED = 2,
      GREEN = 3
    }
    
    export class LevelUtil {
      /**
       * 初始化指定尺寸的网格
       * @param n 网格尺寸(n×n)
       * @returns 初始化后的网格数组
       */
      static initGrid(n: number): Color[][] {
        const grid: Color[][] = [];
        for (let i = 0; i < n; i++) {
          const row: Color[] = [];
          for (let j = 0; j < n; j++) {
            // 随机生成初始颜色,70%概率为非黄色,增加游戏难度
            row.push(Math.random() > 0.3 ? Color.YELLOW : this.getRandomNonYellowColor());
          }
          grid.push(row);
        }
        return grid;
      }
    
      /**
       * 生成随机非黄色
       */
      private static getRandomNonYellowColor(): Color {
        const colors = [Color.BLUE, Color.RED, Color.GREEN];
        return colors[Math.floor(Math.random() * colors.length)];
      }
    
      /**
       * 翻转方块及相邻方块颜色
       * @param grid 当前网格
       * @param row 点击的行索引
       * @param col 点击的列索引
       */
      static flip(grid: Color[][], row: number, col: number): void {
        const n = grid.length;
        
        // 翻转当前方块颜色
        this.flipColor(grid, row, col);
        
        // 翻转上方方块
        if (row > 0) {
          this.flipColor(grid, row - 1, col);
        }
        
        // 翻转下方方块
        if (row < n - 1) {
          this.flipColor(grid, row + 1, col);
        }
        
        // 翻转左方方块
        if (col > 0) {
          this.flipColor(grid, row, col - 1);
        }
        
        // 翻转右方方块
        if (col < n - 1) {
          this.flipColor(grid, row, col + 1);
        }
      }
    
      /**
       * 翻转单个方块颜色
       */
      private static flipColor(grid: Color[][], row: number, col: number): void {
        // 循环切换颜色
        grid[row][col] = (grid[row][col] + 1) % 4;
      }
    
      /**
       * 判定是否所有方块都为黄色
       * @param grid 当前网格
       * @returns 是否通关
       */
      static isAllYellow(grid: Color[][]): boolean {
        for (let i = 0; i < grid.length; i++) {
          for (let j = 0; j < grid[i].length; j++) {
            if (grid[i][j] !== Color.YELLOW) {
              return false;
            }
          }
        }
        return true;
      }
    }
    2.2 关卡数据管理模块

    该模块负责管理关卡进度,实现关卡的保存与读取功能。

    功能说明
  • 记录当前关卡和最大解锁关卡
  • 持久化存储关卡进度
  • 支持关卡重置功能
  • // LevelCache.ts
    export class LevelCache {
      // 内存中保存当前关卡状态
      private static maxUnlocked: number = 1;  // 最大解锁关卡
      private static currentLevel: number = 1; // 当前关卡
      
      /**
       * 获取最大解锁关卡
       */
      static getMaxUnlocked(): number {
        return this.maxUnlocked;
      }
      
      /**
       * 设置最大解锁关卡
       * @param level 关卡数
       */
      static setMaxUnlocked(level: number): void {
        if (level > this.maxUnlocked) {
          this.maxUnlocked = level;
          // 同步保存到本地存储
          PrefUtil.saveMaxUnlocked(level);
        }
      }
      
      /**
       * 获取当前关卡
       */
      static getCurrentLevel(): number {
        return this.currentLevel;
      }
      
      /**
       * 设置当前关卡
       * @param level 关卡数
       */
      static setCurrentLevel(level: number): void {
        // 只能设置已解锁的关卡
        if (level <= this.maxUnlocked) {
          this.currentLevel = level;
        }
      }
      
      /**
       * 初始化关卡数据(从本地加载)
       */
      static async init(): Promise<void> {
        const savedLevel = await PrefUtil.getMaxUnlocked();
        if (savedLevel) {
          this.maxUnlocked = savedLevel;
        }
      }
    }
    
    // PrefUtil.ts
    import dataPreferences from '@ohos.data.preferences';
    
    export class PrefUtil {
      private static PREFERENCES_NAME = 'game_preferences';
      private static KEY_MAX_UNLOCKED = 'max_unlocked_level';
      
      /**
       * 保存最大解锁关卡到本地
       * @param level 关卡数
       */
      static async saveMaxUnlocked(level: number): Promise<void> {
        try {
          const preferences = await dataPreferences.getPreferences(globalThis.context, this.PREFERENCES_NAME);
          await preferences.put(this.KEY_MAX_UNLOCKED, level);
          await preferences.flush();
        } catch (err) {
          console.error(`保存关卡失败: ${err}`);
        }
      }
      
      /**
       * 从本地获取最大解锁关卡
       * @returns 关卡数
       */
      static async getMaxUnlocked(): Promise<number | null> {
        try {
          const preferences = await dataPreferences.getPreferences(globalThis.context, this.PREFERENCES_NAME);
          return await preferences.get(this.KEY_MAX_UNLOCKED, 1);
        } catch (err) {
          console.error(`获取关卡失败: ${err}`);
          return null;
        }
      }
    }
    2.3 UI 界面展示模块

    使用 ArkUI 框架实现游戏界面,包括网格渲染、分数显示和交互处理。

    功能说明
  • 显示当前关卡和步数
  • 渲染方块网格布局
  • 通关动画效果
  • 使用ColumnRow构建页面布局
  • 通过ForEach循环渲染网格中的方块
  • 使用Stack组件实现方块点击区域和视觉效果
  • 通关时通过animation属性实现闪烁动画
  • 长按标题实现关卡重置功能
    • 提供游戏操作按钮(重置本关等)
    • // Index.ts
      import { LevelUtil, Color } from '../common/LevelUtil';
      import { LevelCache } from '../common/LevelCache';
      import router from '@ohos.router';
      
      @Entry
      @Component
      struct GamePage {
        @State grid: Color[][] = [];       // 网格数据
        @State stepCount: number = 0;      // 步数计数
        @State currentLevel: number = 1;   // 当前关卡
        @State isCompleted: boolean = false; // 是否通关
        
        // 页面初始化
        async aboutToAppear() {
          await LevelCache.init();
          this.currentLevel = LevelCache.getCurrentLevel();
          this.resetGame();
        }
        
        // 重置游戏
        resetGame() {
          const size = this.currentLevel + 2; // 关卡+2的网格尺寸
          this.grid = LevelUtil.initGrid(size);
          this.stepCount = 0;
          this.isCompleted = false;
        }
        
        // 处理方块点击
        handleBlockClick(row: number, col: number) {
          if (this.isCompleted) return;
          
          // 翻转颜色
          LevelUtil.flip(this.grid, row, col);
          this.stepCount++;
          
          // 检查是否通关
          if (LevelUtil.isAllYellow(this.grid)) {
            this.isCompleted = true;
            // 解锁下一关
            LevelCache.setMaxUnlocked(this.currentLevel + 1);
          }
        }
        
        // 获取方块颜色
        getBlockColor(color: Color): string {
          switch (color) {
            case Color.YELLOW: return '#FFFF00';
            case Color.BLUE: return '#0000FF';
            case Color.RED: return '#FF0000';
            case Color.GREEN: return '#00FF00';
            default: return '#FFFFFF';
          }
        }
        
        build() {
          Column() {
            // 标题和关卡信息
            Row() {
              Text(`变色方块 - 第${this.currentLevel}关`)
                .fontSize(24)
                .fontWeight(FontWeight.Bold)
            }
            .padding(10)
            
            // 步数显示
            Row() {
              Text(`步数: ${this.stepCount}`)
                .fontSize(18)
            }
            .padding({ bottom: 10 })
            
            // 游戏网格
            Column() {
              ForEach(this.grid, (row: Color[], rowIndex: number) => {
                Row() {
                  ForEach(row, (color: Color, colIndex: number) => {
                    Stack() {
                      // 方块视图
                      Text('')
                        .width(60)
                        .height(60)
                        .backgroundColor(this.getBlockColor(color))
                        .border({ width: 1, color: '#000000' })
                        // 通关动画效果
                        .animation({
                          duration: 500,
                          iterations: this.isCompleted ? 3 : 1
                        })
                    }
                    .onClick(() => this.handleBlockClick(rowIndex, colIndex))
                  })
                }
              })
            }
            
            // 操作按钮
            Row() {
              Button('重置本关')
                .onClick(() => this.resetGame())
                .margin(5)
              
              Button('选择关卡')
                .onClick(() => router.pushUrl({ url: 'pages/LevelSelect' }))
                .margin(5)
              
              if (this.isCompleted) {
                Button('下一关')
                  .onClick(() => {
                    LevelCache.setCurrentLevel(this.currentLevel + 1);
                    this.currentLevel++;
                    this.resetGame();
                  })
                  .margin(5)
              }
            }
            .padding({ top: 20 })
          }
          .width('100%')
          .height('100%')
          .justifyContent(FlexAlign.Center)
          .backgroundColor('#F5F5F5')
        }
      }

      3. 选关页面实现

      // LevelSelect.ts
      import { LevelCache } from '../common/LevelCache';
      import router from '@ohos.router';
      
      @Entry
      @Component
      struct LevelSelectPage {
        @State maxUnlocked: number = 1;
        @State levels: number[] = [];
        
        async aboutToAppear() {
          await LevelCache.init();
          this.maxUnlocked = LevelCache.getMaxUnlocked();
          // 生成1-当前最大解锁关卡+1的数组
          this.levels = Array.from({ length: this.maxUnlocked + 1 }, (_, i) => i + 1);
        }
        
        selectLevel(level: number) {
          LevelCache.setCurrentLevel(level);
          router.back(); // 返回游戏页面
        }
        
        build() {
          Column() {
            Text('选择关卡')
              .fontSize(24)
              .fontWeight(FontWeight.Bold)
              .padding(20)
            
            // 关卡列表
            Grid() {
              ForEach(this.levels, (level: number) => {
                GridItem() {
                  Button(`第${level}关`)
                    .width(100)
                    .height(100)
                    .fontSize(18)
                    .onClick(() => this.selectLevel(level))
                    // 已解锁关卡和未解锁关卡样式区分
                    .backgroundColor(level <= this.maxUnlocked ? '#4CAF50' : '#CCCCCC')
                    .enabled(level <= this.maxUnlocked)
                }
              })
            }
            .columnsTemplate('1fr 1fr 1fr')
            .columnsGap(10)
            .rowsGap(10)
            .padding(10)
          }
          .width('100%')
          .height('100%')
          .backgroundColor('#F5F5F5')
        }
      }

    • 关键技术

      1. OpenHarmony 应用开发基础

    • ArkUI 框架:采用声明式 UI 语法,通过组件组合构建界面,简化 UI 开发流程
    • 状态管理:使用@State装饰器实现数据与 UI 的双向绑定,状态变化时自动更新界面
    • 路由管理:通过router模块实现页面跳转,管理应用导航
    • 2. 数据持久化存储

      使用 OpenHarmony 提供的dataPreferences模块实现关卡进度的本地存储:

    • 数据以键值对形式存储,适合保存简单的应用配置和用户进度
    • 支持异步操作,确保不会阻塞 UI 线程
    • 数据持久化,应用重启后仍可读取
    • 3. 动画效果实现

      通过组件的animation属性实现通关时的闪烁效果:

      typescript

      运行

      .animation({
        duration: 500,      // 动画持续时间
        iterations: 3       // 动画重复次数
      })
      

      4. 测试框架使用

    • Hypium:OpenHarmony 官方测试框架,提供测试用例组织、执行和结果展示能力
    • 测试用例编写采用describe-it结构,清晰组织测试逻辑
    • 使用expect进行断言,验证功能正确性
    • 5. 项目构建工具

    • Hvigor:OpenHarmony 应用的构建工具,负责编译、打包等流程
    • 构建日志保存在.hvigor/report目录,便于排查构建问题
    • 总结展望

      项目总结

      本项目实现了一个功能完整的变色方块游戏,包含以下特点:

    • 完整的游戏逻辑:实现了网格初始化、颜色翻转、通关判定等核心功能
    • 对于初学者来说,通过本项目可以掌握:

    • OpenHarmony 应用的基本结构和开发流程
    • 声明式 UI 的设计与实现方法
    • 状态管理和数据持久化的应用
    • 简单游戏逻辑的设计思路
    • 未来展望

      本项目可以从以下几个方向进行扩展:

    • 增加游戏难度:提供不同难度模式,如限制步数、增加颜色种类等
    • 丰富游戏内容:添加音效反馈、关卡倒计时、提示功能等
    • 优化用户体验:实现方块翻转动画、通关特效、排行榜功能
    • 扩展游戏模式:增加双人对战、限时挑战等模式
    • 个性化设置:提供皮肤更换、背景切换等个性化选项
    • 通过不断优化和扩展,可以将这款简单的小游戏打造成功能丰富、体验优良的休闲娱乐应用,进一步提升对 OpenHarmony 应用开发的理解和掌握。

      希望本文能为 OpenHarmony 初学者提供有价值的参考,帮助大家快速入门并掌握应用开发的基本技能。如果在实践过程中遇到问题,欢迎交流讨论!

      Logo

      开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

      更多推荐