概述

本指南将介绍如何使用HarmonyOS 5的CodeGenie工具快速开发一个简单的保龄球小游戏。游戏将包含物理模拟、得分计算和简单的动画效果。

游戏功能设计

  1. 保龄球滚动控制
  2. 球瓶碰撞检测
  3. 得分计算系统
  4. 游戏状态管理
  5. 简单动画效果

开发步骤

1. 项目创建

  1. 打开DevEco Studio
  2. 选择"Create Project"
  3. 选择"Application" -> "Empty Ability"
  4. 项目命名为"BowlingGame"
  5. 确保勾选"Enable CodeGenie"选项

2. 游戏主界面设计 (game_page.xml)

{
  "data": {
    "gameStatus": "ready", // ready, rolling, scoring, gameOver
    "ballPosition": 0,
    "pins": [
      [true, true, true, true], 
      [true, true, true],
      [true, true],
      [true]
    ],
    "score": 0,
    "remainingBalls": 10,
    "power": 50,
    "angle": 0
  },
  "ui": {
    "type": "page",
    "children": [
      {
        "type": "column",
        "width": "100%",
        "height": "100%",
        "background": "#222222",
        "children": [
          // 游戏信息显示
          {
            "type": "row",
            "justifyContent": "space-between",
            "padding": 10,
            "children": [
              {
                "type": "text",
                "text": "得分: $score",
                "color": "#FFFFFF"
              },
              {
                "type": "text",
                "text": "剩余球数: $remainingBalls",
                "color": "#FFFFFF"
              }
            ]
          },
          
          // 游戏区域
          {
            "type": "stack",
            "flex": 1,
            "margin": { "top": 20 },
            "children": [
              // 球道背景
              {
                "type": "image",
                "src": "common/images/lane.png",
                "width": "100%",
                "height": "100%"
              },
              
              // 保龄球
              {
                "type": "image",
                "src": "common/images/ball.png",
                "width": 40,
                "height": 40,
                "position": "absolute",
                "bottom": 50,
                "left": "50% + $angle * 2 - 20 + $ballPosition * 0.1",
                "if": "gameStatus !== 'scoring'"
              },
              
              // 球瓶
              {
                "type": "div",
                "for": "(row, rowIndex) in pins",
                "children": [
                  {
                    "type": "div",
                    "for": "(pin, pinIndex) in row",
                    "if": "pin",
                    "children": [
                      {
                        "type": "image",
                        "src": "common/images/pin.png",
                        "width": 30,
                        "height": 60,
                        "position": "absolute",
                        "bottom": 200 + rowIndex * 40,
                        "left": "50% - 45 + pinIndex * 30 - rowIndex * 15"
                      }
                    ]
                  }
                ]
              }
            ]
          },
          
          // 控制面板
          {
            "type": "column",
            "padding": 20,
            "background": "#333333",
            "children": [
              // 力量控制
              {
                "type": "row",
                "alignItems": "center",
                "margin": { "bottom": 10 },
                "children": [
                  {
                    "type": "text",
                    "text": "力量:",
                    "color": "#FFFFFF",
                    "margin": { "right": 10 }
                  },
                  {
                    "type": "slider",
                    "value": "$power",
                    "min": 0,
                    "max": 100,
                    "onchange": "updatePower",
                    "flex": 1
                  }
                ]
              },
              
              // 角度控制
              {
                "type": "row",
                "alignItems": "center",
                "margin": { "bottom": 20 },
                "children": [
                  {
                    "type": "text",
                    "text": "角度:",
                    "color": "#FFFFFF",
                    "margin": { "right": 10 }
                  },
                  {
                    "type": "slider",
                    "value": "$angle",
                    "min": -20,
                    "max": 20,
                    "onchange": "updateAngle",
                    "flex": 1
                  }
                ]
              },
              
              // 投球按钮
              {
                "type": "button",
                "text": "投球",
                "onclick": "throwBall",
                "if": "gameStatus === 'ready'"
              },
              
              // 重新开始按钮
              {
                "type": "button",
                "text": "重新开始",
                "onclick": "resetGame",
                "if": "gameStatus === 'gameOver'"
              }
            ]
          }
        ]
      }
    ]
  }
}

3. 游戏逻辑实现 (game_page.js)

export default {
  data: {
    gameStatus: "ready", // 游戏状态
    ballPosition: 0,    // 球的位置
    pins: [             // 球瓶状态(true表示站立)
      [true, true, true, true], 
      [true, true, true],
      [true, true],
      [true]
    ],
    score: 0,           // 当前得分
    remainingBalls: 10,  // 剩余球数
    power: 50,          // 投球力量(0-100)
    angle: 0            // 投球角度(-20到20)
  },
  
  onInit() {
    // 初始化游戏
    this.resetGame();
  },
  
  // 更新力量值
  updatePower(event) {
    this.power = event.value;
  },
  
  // 更新角度值
  updateAngle(event) {
    this.angle = event.value;
  },
  
  // 投球
  throwBall() {
    if (this.gameStatus !== "ready") return;
    
    this.gameStatus = "rolling";
    
    // 球滚动动画
    const animationDuration = 2000 - this.power * 15; // 根据力量计算动画时间
    
    let startTime = Date.now();
    const animateBall = () => {
      const elapsed = Date.now() - startTime;
      const progress = Math.min(elapsed / animationDuration, 1);
      
      // 更新球的位置(抛物线运动)
      this.ballPosition = progress;
      
      if (progress < 1) {
        requestAnimationFrame(animateBall);
      } else {
        this.calculateHit();
      }
    };
    
    animateBall();
  },
  
  // 计算击倒的球瓶
  calculateHit() {
    this.gameStatus = "scoring";
    
    // 模拟物理碰撞
    const hitPower = this.power / 100;
    const hitAngle = this.angle / 20;
    
    // 随机性模拟
    const randomness = 0.2;
    
    // 计算每行球瓶被击倒的概率
    setTimeout(() => {
      const newPins = JSON.parse(JSON.stringify(this.pins));
      let pinsKnockedDown = 0;
      let totalPins = 0;
      
      for (let row = 0; row < newPins.length; row++) {
        for (let col = 0; col < newPins[row].length; col++) {
          if (newPins[row][col]) {
            totalPins++;
            
            // 计算击倒概率(基于力量、角度和位置)
            const rowFactor = 0.7 + row * 0.1;
            const colOffset = Math.abs(col - row - hitAngle * 2);
            const hitProbability = Math.max(0, 
              hitPower * rowFactor * (1 - colOffset * 0.2) + (Math.random() * randomness - randomness/2)
            );
            
            if (hitProbability > 0.5) {
              newPins[row][col] = false;
              pinsKnockedDown++;
            }
          }
        }
      }
      
      this.pins = newPins;
      
      // 更新得分
      this.score += pinsKnockedDown;
      
      // 更新剩余球数
      this.remainingBalls--;
      
      // 检查游戏是否结束
      if (this.remainingBalls <= 0 || totalPins === 0) {
        this.gameStatus = "gameOver";
      } else {
        // 重置球位置
        setTimeout(() => {
          this.ballPosition = 0;
          this.gameStatus = "ready";
        }, 1000);
      }
    }, 500);
  },
  
  // 重置游戏
  resetGame() {
    this.gameStatus = "ready";
    this.ballPosition = 0;
    this.pins = [
      [true, true, true, true], 
      [true, true, true],
      [true, true],
      [true]
    ];
    this.score = 0;
    this.remainingBalls = 10;
    this.power = 50;
    this.angle = 0;
  },
  
  // 添加音效(可选)
  playSound(effect) {
    const audio = new Audio(`common/sounds/${effect}.mp3`);
    audio.play();
  }
}

4. 添加动画效果

为了增强游戏体验,我们可以添加一些简单的动画效果:

// 在calculateHit方法中添加击倒动画
calculateHit() {
  // ...之前的代码
  
  // 添加球瓶击倒动画
  this.$element('pinsContainer').animate({
    duration: 300,
    curve: 'ease-out',
    keyframes: [
      { transform: { rotate: '0deg' }, offset: 0.0 },
      { transform: { rotate: '5deg' }, offset: 0.3 },
      { transform: { rotate: '-5deg' }, offset: 0.6 },
      { transform: { rotate: '0deg' }, offset: 1.0 }
    ]
  });
  
  // ...之后的代码
}

5. 添加游戏音效

  1. 在项目中创建resources/rawfile/sounds目录
  2. 添加以下音效文件:
    • roll.mp3 (球滚动声音)
    • hit.mp3 (撞击声音)
    • strike.mp3 (全中声音)
    • gameover.mp3 (游戏结束声音)

更新游戏逻辑以包含音效:

// 在throwBall方法中添加
throwBall() {
  if (this.gameStatus !== "ready") return;
  
  this.playSound("roll");
  // ...其余代码
}

// 在calculateHit方法中添加
calculateHit() {
  this.playSound("hit");
  
  // 如果是全中
  if (pinsKnockedDown === totalPins) {
    this.playSound("strike");
  }
  
  // ...其余代码
}

// 在resetGame方法中添加
resetGame() {
  if (this.gameStatus === "gameOver") {
    this.playSound("gameover");
  }
  // ...其余代码
}

6. 添加高级计分规则

保龄球有特殊的计分规则,我们可以实现更真实的计分系统:

// 在data中添加
data: {
  // ...之前的数据
  frames: [],          // 记录每一轮的数据
  currentFrame: 0,     // 当前轮次(0-9)
  firstThrow: true,    // 是否是第一次投球
  lastScore: 0         // 上一轮得分
},

// 更新calculateHit方法
calculateHit() {
  // ...之前的代码
  
  setTimeout(() => {
    // ...球瓶计算代码
    
    // 更新计分系统
    const frameScore = {
      firstThrow: this.firstThrow ? pinsKnockedDown : null,
      secondThrow: !this.firstThrow ? pinsKnockedDown : null,
      score: 0,
      isStrike: this.firstThrow && pinsKnockedDown === totalPins,
      isSpare: !this.firstThrow && pinsKnockedDown === totalPins
    };
    
    // 如果是第10轮可能有特殊规则
    if (this.currentFrame === 9) {
      // 第10轮特殊处理
    } else {
      if (this.firstThrow) {
        // 第一次投球
        this.firstThrow = false;
        
        if (frameScore.isStrike) {
          // 全中,跳到下一轮
          this.currentFrame++;
          this.firstThrow = true;
        }
      } else {
        // 第二次投球
        this.currentFrame++;
        this.firstThrow = true;
      }
    }
    
    // 计算得分(简化版)
    this.score += pinsKnockedDown;
    
    // 如果是全中或补中,可能有加分
    if (this.frames.length > 0) {
      const lastFrame = this.frames[this.frames.length - 1];
      
      if (lastFrame.isStrike) {
        // 全中加分(后两次投球得分)
        lastFrame.score += pinsKnockedDown;
        this.score += pinsKnockedDown;
      } else if (lastFrame.isSpare && this.firstThrow) {
        // 补中加分(下一次投球得分)
        lastFrame.score += pinsKnockedDown;
        this.score += pinsKnockedDown;
      }
    }
    
    // 保存本轮数据
    frameScore.score = this.score;
    this.frames.push(frameScore);
    
    // ...游戏结束检查代码
  }, 500);
}

7. 添加游戏帮助和设置

// 在game_page.xml中添加帮助按钮和面板
{
  "type": "button",
  "text": "帮助",
  "onclick": "showHelp",
  "position": "absolute",
  "right": 20,
  "top": 20
},

// 帮助面板
{
  "type": "panel",
  "if": "showHelpPanel",
  "width": "80%",
  "height": "60%",
  "background": "#FFFFFF",
  "position": "absolute",
  "left": "10%",
  "top": "20%",
  "children": [
    {
      "type": "text",
      "text": "保龄球游戏帮助",
      "fontSize": 20,
      "margin": { "top": 20, "left": 20 }
    },
    {
      "type": "scroll",
      "flex": 1,
      "padding": 20,
      "children": [
        {
          "type": "text",
          "text": "1. 使用力量滑块调整投球力度\n2. 使用角度滑块调整投球方向\n3. 点击'投球'按钮投出保龄球\n4. 击倒球瓶获得分数\n5. 全中(一次击倒所有球瓶)有额外加分"
        }
      ]
    },
    {
      "type": "button",
      "text": "关闭",
      "onclick": "hideHelp",
      "margin": { "bottom": 20 }
    }
  ]
}

完整游戏逻辑 (game_page.js)

export default {
  data: {
    gameStatus: "ready",
    ballPosition: 0,
    pins: [
      [true, true, true, true], 
      [true, true, true],
      [true, true],
      [true]
    ],
    score: 0,
    remainingBalls: 10,
    power: 50,
    angle: 0,
    frames: [],
    currentFrame: 0,
    firstThrow: true,
    showHelpPanel: false
  },
  
  onInit() {
    this.resetGame();
  },
  
  updatePower(event) {
    this.power = event.value;
  },
  
  updateAngle(event) {
    this.angle = event.value;
  },
  
  throwBall() {
    if (this.gameStatus !== "ready") return;
    
    this.gameStatus = "rolling";
    this.playSound("roll");
    
    const animationDuration = 2000 - this.power * 15;
    let startTime = Date.now();
    
    const animateBall = () => {
      const elapsed = Date.now() - startTime;
      const progress = Math.min(elapsed / animationDuration, 1);
      
      this.ballPosition = progress;
      
      if (progress < 1) {
        requestAnimationFrame(animateBall);
      } else {
        this.calculateHit();
      }
    };
    
    animateBall();
  },
  
  calculateHit() {
    this.gameStatus = "scoring";
    this.playSound("hit");
    
    const hitPower = this.power / 100;
    const hitAngle = this.angle / 20;
    const randomness = 0.2;
    
    setTimeout(() => {
      const newPins = JSON.parse(JSON.stringify(this.pins));
      let pinsKnockedDown = 0;
      let totalPins = 0;
      
      // 计算击倒的球瓶
      for (let row = 0; row < newPins.length; row++) {
        for (let col = 0; col < newPins[row].length; col++) {
          if (newPins[row][col]) {
            totalPins++;
            const rowFactor = 0.7 + row * 0.1;
            const colOffset = Math.abs(col - row - hitAngle * 2);
            const hitProbability = Math.max(0, 
              hitPower * rowFactor * (1 - colOffset * 0.2) + (Math.random() * randomness - randomness/2)
            );
            
            if (hitProbability > 0.5) {
              newPins[row][col] = false;
              pinsKnockedDown++;
            }
          }
        }
      }
      
      this.pins = newPins;
      
      // 处理计分逻辑
      const frameScore = {
        firstThrow: this.firstThrow ? pinsKnockedDown : null,
        secondThrow: !this.firstThrow ? pinsKnockedDown : null,
        score: 0,
        isStrike: this.firstThrow && pinsKnockedDown === totalPins,
        isSpare: !this.firstThrow && pinsKnockedDown === totalPins
      };
      
      // 处理轮次逻辑
      if (this.currentFrame === 9) {
        // 第10轮特殊处理
        this.remainingBalls--;
      } else {
        if (this.firstThrow) {
          this.firstThrow = false;
          if (frameScore.isStrike) {
            this.currentFrame++;
            this.firstThrow = true;
          }
        } else {
          this.currentFrame++;
          this.firstThrow = true;
        }
        this.remainingBalls--;
      }
      
      // 计算得分
      this.score += pinsKnockedDown;
      
      // 处理全中和补中加分
      if (this.frames.length > 0) {
        const lastFrame = this.frames[this.frames.length - 1];
        
        if (lastFrame.isStrike) {
          lastFrame.score += pinsKnockedDown;
          this.score += pinsKnockedDown;
        } else if (lastFrame.isSpare && this.firstThrow) {
          lastFrame.score += pinsKnockedDown;
          this.score += pinsKnockedDown;
        }
      }
      
      // 保存本轮数据
      frameScore.score = this.score;
      this.frames.push(frameScore);
      
      // 播放全中音效
      if (frameScore.isStrike) {
        this.playSound("strike");
      }
      
      // 检查游戏是否结束
      if (this.remainingBalls <= 0 || (this.currentFrame >= 9 && !frameScore.isStrike && !frameScore.isSpare)) {
        this.gameStatus = "gameOver";
        this.playSound("gameover");
      } else {
        setTimeout(() => {
          this.ballPosition = 0;
          this.gameStatus = "ready";
        }, 1000);
      }
    }, 500);
  },
  
  resetGame() {
    this.gameStatus = "ready";
    this.ballPosition = 0;
    this.pins = [
      [true, true, true, true], 
      [true, true, true],
      [true, true],
      [true]
    ];
    this.score = 0;
    this.remainingBalls = 10;
    this.power = 50;
    this.angle = 0;
    this.frames = [];
    this.currentFrame = 0;
    this.firstThrow = true;
  },
  
  playSound(effect) {
    const audio = new Audio(`common/sounds/${effect}.mp3`);
    audio.play().catch(e => console.log("音效播放失败:", e));
  },
  
  showHelp() {
    this.showHelpPanel = true;
  },
  
  hideHelp() {
    this.showHelpPanel = false;
  }
}

总结

通过HarmonyOS 5的CodeGenie工具,我们开发了一个功能完整的保龄球小游戏,包含以下特点:

  1. ​直观的游戏控制​​:通过滑块调整投球力量和角度
  2. ​物理模拟​​:简单的碰撞检测和球瓶击倒逻辑
  3. ​计分系统​​:实现基本得分和全中/补中加分规则
  4. ​游戏状态管理​​:处理准备、投球、计分和游戏结束等状态
  5. ​音效和动画​​:增强游戏体验的视听效果
  6. ​帮助系统​​:提供游戏玩法和规则说明

这个示例展示了如何使用CodeGenie快速开发游戏类应用,您可以根据需要进一步扩展功能,如添加多人模式、成就系统或更复杂的物理引擎。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐