在鸿蒙HarmonyOS 5中使用CodeGenie实现一个森林冰火人小游戏

应用概述

森林冰火人(Fireboy and Watergirl)是一款经典的双角色解谜游戏,玩家需要同时控制火男孩和水女孩,利用各自特性通过关卡。

开发步骤

1. 创建项目

  1. 打开CodeGenie IDE
  2. 选择"File" > "New" > "HarmonyOS Project"
  3. 输入项目名称"FireboyWatergirl"
  4. 选择API版本为HarmonyOS 5
  5. 点击"Finish"

2. 准备游戏资源

resources/base/media目录下添加以下资源:

  • 角色精灵图:fireboy.png, watergirl.png
  • 地形元素:ground.png, water.png, lava.png, door.png
  • 特效:splash.png, steam.png
  • 背景音乐和音效

3. 设计游戏数据结构

resources/base/profile目录下创建game_config.json:

{
  "characters": {
    "fireboy": {
      "speed": 5,
      "jumpForce": 10,
      "canWalkOn": ["ground", "lava"],
      "diesIn": ["water"],
      "color": "#FF4500",
      "image": "media/fireboy.png"
    },
    "watergirl": {
      "speed": 5,
      "jumpForce": 10,
      "canWalkOn": ["ground", "water"],
      "diesIn": ["lava"],
      "color": "#1E90FF",
      "image": "media/watergirl.png"
    }
  },
  "levels": [
    {
      "id": 1,
      "name": "森林神殿",
      "map": [
        "GGGGGGGGGGGGGGGGGGGG",
        "G                  G",
        "G   F    W    L    G",
        "G         D    D   G",
        "G   P          P   G",
        "GGGGGGGGGGGGGGGGGGGG"
      ],
      "legend": {
        "G": {"type": "ground", "image": "media/ground.png"},
        " ": {"type": "air"},
        "F": {"type": "fire_door", "image": "media/door.png"},
        "W": {"type": "water_door", "image": "media/door.png"},
        "L": {"type": "lever", "image": "media/lever.png"},
        "D": {"type": "diamond", "image": "media/diamond.png"},
        "P": {"type": "platform", "image": "media/platform.png", "movable": true}
      }
    }
  ]
}

4. 创建游戏主界面

resources/base/layout目录下创建game_page.xml:

<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:width="match_parent"
    ohos:height="match_parent"
    ohos:orientation="vertical">
    
    <Canvas
        ohos:id="$+id:game_canvas"
        ohos:width="match_parent"
        ohos:height="match_parent"/>
        
    <DirectionalLayout
        ohos:width="match_parent"
        ohos:height="60vp"
        ohos:orientation="horizontal"
        ohos:background_element="#80000000"
        ohos:alignment="bottom">
        
        <Button
            ohos:id="$+id:left_btn"
            ohos:width="80vp"
            ohos:height="50vp"
            ohos:text="←"
            ohos:margin="5vp"/>
            
        <Button
            ohos:id="$+id:right_btn"
            ohos:width="80vp"
            ohos:height="50vp"
            ohos:text="→"
            ohos:margin="5vp"/>
            
        <Button
            ohos:id="$+id:jump_btn"
            ohos:width="80vp"
            ohos:height="50vp"
            ohos:text="跳跃"
            ohos:margin="5vp"/>
            
        <Button
            ohos:id="$+id:switch_btn"
            ohos:width="80vp"
            ohos:height="50vp"
            ohos:text="切换"
            ohos:margin="5vp"/>
    </DirectionalLayout>
</DirectionalLayout>

5. 实现游戏引擎

src/main/ets/MainAbility目录下创建GameEngine.ets:

import gameConfig from '../../resources/base/profile/game_config.json';
import { Character, Level, GameMap, GameObject } from '../common/model';

export class GameEngine {
  private canvas: CanvasRenderingContext2D | null = null;
  private characters: Record<string, Character> = {};
  private currentLevel: Level;
  private gameMap: GameMap = [];
  private activeCharacter: string = 'fireboy';
  private gameObjects: GameObject[] = [];
  private lastTime: number = 0;
  private isRunning: boolean = false;

  constructor(canvas: CanvasRenderingContext2D, level: Level) {
    this.canvas = canvas;
    this.currentLevel = level;
    this.initCharacters();
    this.parseLevelMap();
  }

  private initCharacters() {
    this.characters = {
      fireboy: {
        ...gameConfig.characters.fireboy,
        x: 100,
        y: 300,
        width: 40,
        height: 60,
        velocityX: 0,
        velocityY: 0,
        isJumping: false,
        collectedDiamonds: 0
      },
      watergirl: {
        ...gameConfig.characters.watergirl,
        x: 150,
        y: 300,
        width: 40,
        height: 60,
        velocityX: 0,
        velocityY: 0,
        isJumping: false,
        collectedDiamonds: 0
      }
    };
  }

  private parseLevelMap() {
    const { map, legend } = this.currentLevel;
    const tileSize = 40;
    
    for (let y = 0; y < map.length; y++) {
      const row = map[y];
      this.gameMap[y] = [];
      
      for (let x = 0; x < row.length; x++) {
        const tile = row[x];
        const tileConfig = legend[tile];
        
        if (tileConfig) {
          this.gameMap[y][x] = {
            ...tileConfig,
            x: x * tileSize,
            y: y * tileSize,
            width: tileSize,
            height: tileSize
          };
          
          if (tileConfig.type !== 'air') {
            this.gameObjects.push(this.gameMap[y][x]);
          }
        }
      }
    }
  }

  start() {
    this.isRunning = true;
    this.lastTime = Date.now();
    this.gameLoop();
  }

  stop() {
    this.isRunning = false;
  }

  private gameLoop() {
    if (!this.isRunning || !this.canvas) return;

    const now = Date.now();
    const deltaTime = (now - this.lastTime) / 1000;
    this.lastTime = now;

    this.update(deltaTime);
    this.render();

    requestAnimationFrame(() => this.gameLoop());
  }

  private update(deltaTime: number) {
    // 更新角色位置
    Object.values(this.characters).forEach(character => {
      // 应用重力
      character.velocityY += 20 * deltaTime;
      
      // 更新位置
      character.x += character.velocityX * deltaTime;
      character.y += character.velocityY * deltaTime;
      
      // 检测碰撞
      this.checkCollisions(character);
    });
    
    // 检查游戏状态
    this.checkGameStatus();
  }

  private checkCollisions(character: Character) {
    let onGround = false;
    
    this.gameObjects.forEach(obj => {
      if (this.isColliding(character, obj)) {
        // 处理不同类型的碰撞
        switch (obj.type) {
          case 'ground':
          case 'platform':
            this.handleGroundCollision(character, obj);
            onGround = true;
            break;
            
          case 'water':
            if (character.canWalkOn.includes('water')) {
              this.handleWaterCollision(character, obj);
            } else {
              // 火男孩遇水死亡
              this.characters.fireboy = this.resetCharacter(this.characters.fireboy);
            }
            break;
            
          case 'lava':
            if (character.canWalkOn.includes('lava')) {
              this.handleLavaCollision(character, obj);
            } else {
              // 水女孩遇岩浆死亡
              this.characters.watergirl = this.resetCharacter(this.characters.watergirl);
            }
            break;
            
          case 'diamond':
            this.handleDiamondCollision(character, obj);
            break;
            
          case 'fire_door':
          case 'water_door':
            this.handleDoorCollision(character, obj);
            break;
            
          case 'lever':
            this.handleLeverCollision(character, obj);
            break;
        }
      }
    });
    
    character.isJumping = !onGround;
  }

  private isColliding(a: GameObject, b: GameObject): boolean {
    return a.x < b.x + b.width &&
           a.x + a.width > b.x &&
           a.y < b.y + b.height &&
           a.y + a.height > b.y;
  }

  private handleGroundCollision(character: Character, ground: GameObject) {
    // 简单的碰撞响应 - 将角色放在地面之上
    character.y = ground.y - character.height;
    character.velocityY = 0;
  }

  private resetCharacter(character: Character): Character {
    // 重置角色到起始位置
    return {
      ...character,
      x: character === this.characters.fireboy ? 100 : 150,
      y: 300,
      velocityX: 0,
      velocityY: 0,
      isJumping: false
    };
  }

  private checkGameStatus() {
    // 检查是否两个角色都到达了各自的门
    const fireboy = this.characters.fireboy;
    const watergirl = this.characters.watergirl;
    
    const fireboyAtDoor = this.gameObjects.some(obj => 
      obj.type === 'fire_door' && this.isColliding(fireboy, obj)
    );
    
    const watergirlAtDoor = this.gameObjects.some(obj => 
      obj.type === 'water_door' && this.isColliding(watergirl, obj)
    );
    
    if (fireboyAtDoor && watergirlAtDoor) {
      this.stop();
      prompt.showToast({ message: '关卡完成!', duration: 2000 });
    }
  }

  private render() {
    if (!this.canvas) return;
    
    // 清空画布
    this.canvas.clearRect(0, 0, this.canvas.width, this.canvas.height);
    
    // 绘制地图
    this.gameObjects.forEach(obj => {
      if (obj.image) {
        const img = new Image();
        img.src = obj.image;
        this.canvas?.drawImage(img, obj.x, obj.y, obj.width, obj.height);
      }
    });
    
    // 绘制角色
    Object.entries(this.characters).forEach(([name, character]) => {
      const img = new Image();
      img.src = character.image;
      this.canvas?.drawImage(img, character.x, character.y, character.width, character.height);
      
      // 高亮显示当前活动角色
      if (name === this.activeCharacter) {
        this.canvas.strokeStyle = '#FFFFFF';
        this.canvas.lineWidth = 2;
        this.canvas.strokeRect(character.x, character.y, character.width, character.height);
      }
    });
  }

  // 控制方法
  moveCharacter(direction: 'left' | 'right') {
    const character = this.characters[this.activeCharacter];
    const speed = character.speed;
    
    character.velocityX = direction === 'left' ? -speed : speed;
  }

  stopCharacter() {
    this.characters[this.activeCharacter].velocityX = 0;
  }

  jumpCharacter() {
    const character = this.characters[this.activeCharacter];
    
    if (!character.isJumping) {
      character.velocityY = -character.jumpForce;
      character.isJumping = true;
    }
  }

  switchCharacter() {
    this.activeCharacter = this.activeCharacter === 'fireboy' ? 'watergirl' : 'fireboy';
  }
}

6. 实现游戏页面

创建GamePage.ets:

import { GameEngine } from './GameEngine';
import gameConfig from '../../resources/base/profile/game_config.json';
import { Level } from '../common/model';

@Entry
@Component
struct GamePage {
  private canvasRef: CanvasRenderingContext2D | null = null;
  private gameEngine: GameEngine | null = null;
  private currentLevel: Level = gameConfig.levels[0];

  onPageShow() {
    if (this.canvasRef) {
      this.gameEngine = new GameEngine(this.canvasRef, this.currentLevel);
      this.gameEngine.start();
    }
  }

  onPageHide() {
    if (this.gameEngine) {
      this.gameEngine.stop();
    }
  }

  build() {
    Column() {
      Canvas(this.canvasRef)
        .width('100%')
        .height('80%')
        .onReady((canvas: CanvasRenderingContext2D) => {
          this.canvasRef = canvas;
        })
      
      Row() {
        Button('←')
          .width(80)
          .height(50)
          .margin(5)
          .onTouch((event: TouchEvent) => {
            if (event.type === TouchType.Down) {
              this.gameEngine?.moveCharacter('left');
            } else if (event.type === TouchType.Up) {
              this.gameEngine?.stopCharacter();
            }
          })
        
        Button('→')
          .width(80)
          .height(50)
          .margin(5)
          .onTouch((event: TouchEvent) => {
            if (event.type === TouchType.Down) {
              this.gameEngine?.moveCharacter('right');
            } else if (event.type === TouchType.Up) {
              this.gameEngine?.stopCharacter();
            }
          })
        
        Button('跳跃')
          .width(80)
          .height(50)
          .margin(5)
          .onClick(() => {
            this.gameEngine?.jumpCharacter();
          })
        
        Button('切换')
          .width(80)
          .height(50)
          .margin(5)
          .onClick(() => {
            this.gameEngine?.switchCharacter();
          })
      }
      .width('100%')
      .height(60)
      .justifyContent(FlexAlign.SpaceAround)
    }
  }
}

7. 创建关卡选择界面

resources/base/layout目录下创建level_select_page.xml:

<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:width="match_parent"
    ohos:height="match_parent"
    ohos:orientation="vertical"
    ohos:padding="20vp">
    
    <Text
        ohos:width="match_parent"
        ohos:height="wrap_content"
        ohos:text="选择关卡"
        ohos:text_size="24fp"
        ohos:text_alignment="center"
        ohos:margin="20vp"/>
        
    <GridLayout
        ohos:id="$+id:level_grid"
        ohos:width="match_parent"
        ohos:height="match_parent"
        ohos:column_count="3"
        ohos:row_count="2"/>
</DirectionalLayout>

8. 实现关卡选择逻辑

创建LevelSelectPage.ets:

import gameConfig from '../../resources/base/profile/game_config.json';
import { Level } from '../common/model';

@Entry
@Component
struct LevelSelectPage {
  @State levels: Array<Level> = gameConfig.levels;

  build() {
    Column() {
      Text('选择关卡')
        .fontSize(24)
        .textAlign(TextAlign.Center)
        .margin(20)
      
      Grid() {
        ForEach(this.levels, (level: Level) => {
          GridItem() {
            Column() {
              Image($r('app.media.level_icon'))
                .width(60)
                .height(60)
                .margin(10)
              
              Text(level.name)
                .fontSize(16)
                .margin({ bottom: 10 })
            }
            .width('90%')
            .height(120)
            .borderRadius(10)
            .backgroundColor('#333333')
            .onClick(() => {
              router.push({
                url: 'pages/GamePage',
                params: { levelId: level.id }
              })
            })
          }
        })
      }
      .columnsTemplate('1fr 1fr 1fr')
      .rowsTemplate('1fr 1fr')
      .width('100%')
      .height('80%')
    }
  }
}

9. 添加类型定义

src/main/ets/common目录下创建model.ets:

export interface Character {
  x: number;
  y: number;
  width: number;
  height: number;
  speed: number;
  jumpForce: number;
  canWalkOn: string[];
  diesIn: string[];
  color: string;
  image: string;
  velocityX: number;
  velocityY: number;
  isJumping: boolean;
  collectedDiamonds: number;
}

export interface GameObject {
  type: string;
  x: number;
  y: number;
  width: number;
  height: number;
  image?: string;
  movable?: boolean;
}

export interface Level {
  id: number;
  name: string;
  map: string[];
  legend: Record<string, GameObject>;
}

export interface GameMap {
  [y: number]: GameObject[];
}

10. 修改入口页面

修改src/main/ets/MainAbility目录下的MainAbility.ts:

export default class MainAbility extends Ability {
  onWindowStageCreate(windowStage: window.WindowStage) {
    windowStage.loadContent('pages/LevelSelectPage', (err, data) => {
      if (err.code) {
        console.error('Failed to load the content. Cause:' + JSON.stringify(err));
        return;
      }
      console.info('Succeeded in loading the content. Data: ' + JSON.stringify(data));
    });
  }
}

功能扩展建议

  1. ​更多关卡设计​​:添加更多精心设计的关卡
  2. ​角色动画​​:添加行走、跳跃等动画效果
  3. ​特效系统​​:水花、蒸汽等粒子效果
  4. ​音效系统​​:背景音乐和游戏音效
  5. ​存档系统​​:保存游戏进度
  6. ​多人模式​​:支持两个玩家同时操作
  7. ​编辑器功能​​:允许玩家创建自定义关卡
  8. ​成就系统​​:完成特定挑战解锁成就

测试与发布

  1. 在CodeGenie中使用预览器测试游戏界面
  2. 测试角色移动和碰撞检测
  3. 测试关卡切换和游戏逻辑
  4. 在真机设备上测试触摸控制
  5. 使用HUAWEI AppGallery Connect进行应用签名和发布

这个森林冰火人小游戏将充分利用鸿蒙系统的图形渲染能力和触摸交互,为玩家带来有趣的解谜体验。通过双角色协作机制,培养玩家的逻辑思维和协作能力。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值