项目地址https://atomgit.com/feng8403000/fps60-renwuduizhan

一、项目概述

本项目基于 HarmonyOS 6.1 开发了一款 PC 端人物战斗游戏,实现了流畅的 60fps 人物行走动画、键盘控制系统、敌人对战系统以及完整的游戏交互逻辑。

1.1 核心功能特性

功能模块 功能描述 技术实现
人物动画 60fps流畅行走动画,包含身体摆动、手臂摆动、腿部摆动 三角函数驱动的相位计算
键盘控制 A/D移动、U攻击、I跳跃 事件监听与状态机管理
敌人系统 敌对角色、血槽显示、战败躺下动画 状态管理与条件渲染
攻击系统 攻击波发射、碰撞检测、伤害计算 动画帧更新与边界检测
跳跃系统 物理跳跃效果、重力模拟 抛物线运动方程
边界处理 全屏移动范围、自动限制 坐标边界检测

1.2 游戏操作指南

按键 功能 按钮显示
A 向左移动 A-后退
D 向右移动 D-前进
U 发射攻击波 U-发波
I 跳跃 I-跳跃

二、核心架构设计

2.1 整体架构图

┌─────────────────────────────────────────────────────────────┐
│                      游戏主页面 (Entry)                     │
├─────────────────────────────────────────────────────────────┤
│  ┌───────────────┐  ┌───────────────┐  ┌───────────────┐  │
│  │  状态管理层   │  │  动画计算层   │  │  事件处理层   │  │
│  │  (@State)     │  │  (Phase)      │  │  (KeyEvent)   │  │
│  └──────┬────────┘  └──────┬────────┘  └──────┬────────┘  │
│         │                  │                  │            │
│         ▼                  ▼                  ▼            │
│  ┌──────────────────────────────────────────────────────┐  │
│  │                    渲染层 (Builders)                  │  │
│  │  buildWalkingCharacter | buildEnemy | buildAttackWave│  │
│  │  buildHealthBars | buildGround | buildInfoPanel      │  │
│  └──────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘

2.2 状态管理设计

// 核心状态变量设计
@State characterX: number = 50;      // 玩家X坐标
@State isWalking: boolean = false;    // 是否行走
@State moveDirection: string = 'none'; // 移动方向
@State walkPhase: number = 0;         // 行走相位(0-360°)
@State jumpHeight: number = 0;        // 跳跃高度
@State isJumping: boolean = false;    // 是否跳跃
@State attackWaveVisible: boolean = false; // 攻击波可见
@State attackWaveX: number = 0;       // 攻击波X坐标
@State enemyHealth: number = 100;     // 敌人血量
@State playerHealth: number = 100;    // 玩家血量
@State enemyDefeated: boolean = false; // 敌人是否战败

状态设计原则:

  • 分离关注点:位置、动作、状态各自独立
  • 单向数据流:状态变化驱动UI更新
  • 最小化状态:只存储必要的游戏状态

三、60fps 流畅动画实现

3.1 动画驱动机制

// 动画帧更新定时器
private animationTimer: number = 0;
private frameCount: number = 0;

onPageShow() {
  // 60fps = 16.67ms 间隔
  this.animationTimer = setInterval(() => {
    this.updateAnimation();
    this.frameCount++;
  }, 16) as unknown as number;
}

updateAnimation() {
  // 更新行走相位
  if (this.isWalking) {
    this.walkPhase += 12; // 每帧增加12度
    if (this.walkPhase >= 360) {
      this.walkPhase = 0;
    }
    // 根据方向移动
    if (this.moveDirection === 'left') {
      this.characterX = Math.max(0, this.characterX - 5);
    } else if (this.moveDirection === 'right') {
      this.characterX = Math.min(850, this.characterX + 5);
    }
  }
  
  // 更新跳跃物理
  if (this.isJumping) {
    this.jumpVelocity -= 0.8;
    this.jumpHeight += this.jumpVelocity;
    if (this.jumpHeight <= 0) {
      this.jumpHeight = 0;
      this.isJumping = false;
      this.jumpVelocity = 0;
    }
  }
  
  // 更新攻击波
  if (this.attackWaveVisible) {
    this.attackWaveX += this.moveDirection === 'left' ? -15 : 15;
    // 检测与敌人碰撞
    if (this.attackWaveX >= this.enemyX - 30 && 
        this.attackWaveX <= this.enemyX + 30) {
      this.enemyHealth = Math.max(0, this.enemyHealth - 10);
      this.attackWaveVisible = false;
      if (this.enemyHealth <= 0) {
        this.enemyDefeated = true;
      }
    }
    // 超出边界
    if (this.attackWaveX < -50 || this.attackWaveX > 950) {
      this.attackWaveVisible = false;
    }
  }
}

3.2 人物动画参数计算

// 手臂摆动角度计算
get leftArmAngle(): number {
  return Math.sin(this.walkPhase * Math.PI / 180) * 30;
}

// 腿部摆动角度计算
get leftLegAngle(): number {
  return Math.sin((this.walkPhase - 90) * Math.PI / 180) * 25;
}

// 身体上下摆动
get bodyBob(): number {
  return Math.sin(this.walkPhase * Math.PI / 180) * 5;
}

// 方向文本获取
getDirectionText(): string {
  if (this.moveDirection === 'left') return '后退';
  if (this.moveDirection === 'right') return '前进';
  return '静止';
}

动画流畅性保障:

  • 使用 setInterval 以 16ms 间隔更新(约60fps)
  • 相位增量控制:每帧增加12度,30帧完成一个周期
  • 三角函数计算:使用 sin 函数实现平滑摆动

四、键盘事件处理系统

4.1 事件监听与处理

// 键盘事件处理
handleKeyEvent(event: Object): void {
  let eventObj: Record<string, string | number> = event as Record<string, string | number>;
  let keyText: string = eventObj['keyText'] ? eventObj['keyText'] as string : '';
  let eventType: number = eventObj['type'] as number;
  
  // 按键按下事件
  if (eventType === 1) {
    switch (keyText.toUpperCase()) {
      case 'A':
        this.moveDirection = 'left';
        this.isWalking = true;
        break;
      case 'D':
        this.moveDirection = 'right';
        this.isWalking = true;
        break;
      case 'U':
        this.attack();
        break;
      case 'I':
        this.jump();
        break;
    }
  }
  
  // 按键释放事件
  if (eventType === 0) {
    if (keyText.toUpperCase() === 'A' && this.moveDirection === 'left') {
      this.isWalking = false;
    }
    if (keyText.toUpperCase() === 'D' && this.moveDirection === 'right') {
      this.isWalking = false;
    }
  }
}

4.2 攻击与跳跃方法

// 攻击方法
attack(): void {
  if (!this.attackWaveVisible) {
    this.attackWaveVisible = true;
    this.attackWaveX = this.characterX + (this.moveDirection === 'left' ? -10 : 40);
    this.attackWavePhase = 0;
  }
}

// 跳跃方法
jump(): void {
  if (!this.isJumping) {
    this.isJumping = true;
    this.jumpVelocity = 12;
  }
}

键盘控制特点:

  • 支持按键按下和释放事件
  • 持续按住 A/D 可连续移动
  • 支持大小写不敏感的按键识别
  • 使用 keyText 而非 keyCode 提高兼容性

五、敌人对战系统

5.1 敌人状态管理

@State enemyX: number = 700;        // 敌人初始位置
@State enemyY: number = 0;
@State enemyHealth: number = 100;    // 敌人血量
@State enemyDefeated: boolean = false; // 战败状态

5.2 敌人渲染逻辑

@Builder
buildEnemy() {
  Stack({ alignContent: Alignment.Bottom }) {
    if (this.enemyDefeated) {
      // 战败状态:躺下
      Column({ space: 0 }) {
        Row({ space: 0 }) {
          Circle()
            .width(30)
            .height(30)
            .fill('#FF6B6B');
          Rect()
            .width(35)
            .height(24)
            .fill('#8B0000')
            .borderRadius(5)
            .translate({ y: 3 });
          Stack({ alignContent: Alignment.Top }) {
            Rect()
              .width(30)
              .height(10)
              .fill('#1a1a1a')
              .borderRadius(3);
          }
          .width(30)
          .height(30);
        }
      }
      .rotate({ x: 0, y: 0, z: 1, angle: 90 })
      .translate({ y: -50 });
    } else {
      // 正常状态:站立
      Column({ space: 0 }) {
        // 头部、身体、四肢渲染...
      }
      .translate({ y: -this.bodyBob });
    }
  }
  .width(50)
  .height(120)
  .translate({ x: this.enemyX, y: -330 });
}

5.3 血槽面板实现

@Builder
buildHealthBars() {
  Row({ space: 40 }) {
    // 玩家血槽
    Column({ space: 4 }) {
      Text('玩家')
        .fontSize(12)
        .fontColor('#ffffff');
      Stack({ alignContent: Alignment.Start }) {
        Rect()
          .width(200)
          .height(15)
          .fill('rgba(255,255,255,0.2)')
          .borderRadius(7);
        Rect()
          .width(this.playerHealth * 2)
          .height(15)
          .fill('#22c55e')
          .borderRadius(7);
      }
      .width(200);
    }
    
    // 敌人血槽
    Column({ space: 4 }) {
      Text('敌人')
        .fontSize(12)
        .fontColor('#ffffff');
      Stack({ alignContent: Alignment.Start }) {
        Rect()
          .width(200)
          .height(15)
          .fill('rgba(255,255,255,0.2)')
          .borderRadius(7);
        Rect()
          .width(this.enemyHealth * 2)
          .height(15)
          .fill('#ef4444')
          .borderRadius(7);
      }
      .width(200);
    }
  }
  .padding({ top: 30, left: 50 });
}

六、碰撞检测与边界处理

6.1 攻击波碰撞检测

// 在 updateAnimation() 中执行
if (this.attackWaveVisible) {
  this.attackWaveX += this.moveDirection === 'left' ? -15 : 15;
  
  // 碰撞检测:攻击波与敌人重叠
  if (this.attackWaveX >= this.enemyX - 30 && 
      this.attackWaveX <= this.enemyX + 30) {
    this.enemyHealth = Math.max(0, this.enemyHealth - 10);
    this.attackWaveVisible = false;
    
    // 判断敌人是否战败
    if (this.enemyHealth <= 0) {
      this.enemyDefeated = true;
    }
  }
  
  // 边界检测:攻击波超出屏幕
  if (this.attackWaveX < -50 || this.attackWaveX > 950) {
    this.attackWaveVisible = false;
  }
}

6.2 人物移动边界限制

// 移动时进行边界检查
if (this.moveDirection === 'left') {
  this.characterX = Math.max(0, this.characterX - 5);      // 左边界
} else if (this.moveDirection === 'right') {
  this.characterX = Math.min(850, this.characterX + 5);    // 右边界
}

边界处理策略:

  • 使用 Math.max/min 实现边界钳制
  • 攻击波使用独立的边界检查
  • 敌人位置固定,玩家可全屏移动

七、UI布局与交互设计

7.1 信息面板布局

@Builder
buildInfoPanel() {
  Row({ space: 30 }) {
    // 左侧:动画参数
    Row({ space: 20 }) {
      Column({ space: 4 }) {
        Text('姿态').fontSize(10).fontColor('rgba(255,255,255,0.5)');
        Text(this.walkPhase.toFixed(0) + '°').fontSize(11).fontColor('#22c55e');
      }
      Column({ space: 4 }) {
        Text('位置').fontSize(10).fontColor('rgba(255,255,255,0.5)');
        Text(this.characterX.toFixed(0) + 'px').fontSize(11).fontColor('#3b82f6');
      }
      Column({ space: 4 }) {
        Text('跳跃').fontSize(10).fontColor('rgba(255,255,255,0.5)');
        Text(this.jumpHeight.toFixed(0) + 'px').fontSize(11).fontColor('#a855f7');
      }
      Column({ space: 4 }) {
        Text('方向').fontSize(10).fontColor('rgba(255,255,255,0.5)');
        Text(this.getDirectionText()).fontSize(11).fontColor(this.getDirectionColor());
      }
    }
    
    // 右侧:控制按钮
    Row({ space: 6 }) {
      Button('A-后退')
        .width(60)
        .height(30)
        .fontSize(12)
        .backgroundColor('rgba(59,130,246,0.3)')
        .fontColor('#3b82f6')
        .border({ width: 1, color: '#3b82f6' })
        .onClick(() => { this.moveDirection = 'left'; this.isWalking = true; });
      // D、U、I 按钮...
    }
  }
  .width('100%')
  .justifyContent(FlexAlign.SpaceBetween)
  .padding({ top: 10, bottom: 15, left: 20, right: 20 })
  .translate({ y: -300 });
}

7.2 场景布局结构

@Builder
buildAnimationScene() {
  Stack({ alignContent: Alignment.Bottom }) {
    // 天空背景
    this.buildClouds();
    this.buildSun();
    this.buildMountains();
    
    // 游戏UI
    this.buildHealthBars();
    
    // 游戏角色
    this.buildWalkingCharacter();
    this.buildEnemy();
    
    // 特效
    this.buildAttackWave();
    
    // 地面
    this.buildGround();
  }
  .width('100%')
  .height('75%')
  .clip(true)
  .borderRadius(20)
  .margin({ left: 20, right: 20 });
}

八、技术难点与解决方案

8.1 键盘事件兼容问题

问题描述:
HarmonyOS PC 端键盘事件处理与移动设备不同,keyCode 返回 <private>,无法直接使用。

解决方案:

// 使用 keyText 而非 keyCode
let keyText: string = eventObj['keyText'] ? eventObj['keyText'] as string : '';

// 处理逻辑
if (keyText.toUpperCase() === 'A') {
  // 左移
}

8.2 动画同步问题

问题描述:
人物、敌人、攻击波的位置需要保持在同一视觉平面。

解决方案:

// 统一使用 translate({ y: -330 }) 向上偏移
.translate({ x: this.characterX, y: -330 - this.jumpHeight }); // 玩家
.translate({ x: this.enemyX, y: -330 });                         // 敌人
.translate({ x: this.attackWaveX, y: -330 - this.jumpHeight });  // 攻击波

8.3 性能优化策略

优化项 实现方式 效果
60fps 帧率 setInterval(16ms) 流畅动画
状态合并 批量更新状态 减少重绘次数
条件渲染 if(this.attackWaveVisible) 避免无效渲染
数学计算 使用 Math.sin/cos GPU加速

九、项目结构总结

9.1 文件结构

entry/
└── src/
    └── main/
        └── ets/
            └── pages/
                └── CharacterWalking.ets  # 主页面
docs/
└── HarmonyOS_Character_Combat_Game.md  # 技术文档

9.2 核心方法清单

方法名 功能描述 调用位置
buildWalkingCharacter() 渲染玩家角色 buildAnimationScene
buildEnemy() 渲染敌人角色 buildAnimationScene
buildAttackWave() 渲染攻击波 buildAnimationScene
buildHealthBars() 渲染血槽面板 buildAnimationScene
buildGround() 渲染地面 buildAnimationScene
buildInfoPanel() 渲染信息面板 build
updateAnimation() 更新动画帧 setInterval
handleKeyEvent() 处理键盘事件 onKeyEvent
attack() 执行攻击 按键/按钮触发
jump() 执行跳跃 按键/按钮触发

十、扩展功能建议

10.1 可扩展功能

功能 描述 实现复杂度
敌人AI 敌人自动移动和攻击
多敌人系统 支持多个敌人
技能系统 多种攻击技能
等级系统 血量、攻击力升级
音效系统 攻击、跳跃音效
存档系统 保存游戏进度

10.2 性能优化方向

  1. 使用 @Builder 复用组件:减少重复代码
  2. 状态懒加载:只在需要时计算
  3. GPU渲染优化:使用硬件加速
  4. 对象池:复用攻击波对象

十一、总结

本项目基于 HarmonyOS 6.1 实现了一个完整的 PC 端人物战斗游戏,包含:

  1. 流畅的 60fps 动画系统:基于三角函数的相位计算
  2. 完整的键盘控制系统:支持 A/D/U/I 按键
  3. 敌人对战系统:血槽、伤害、战败动画
  4. 物理模拟:跳跃重力效果
  5. 边界检测:全屏移动范围限制

项目代码结构清晰,状态管理合理,可扩展性强,是学习 HarmonyOS ArkUI 开发的优秀示例。


技术栈:HarmonyOS 6.1 / ArkTS / ArkUI

开发工具:DevEco Studio

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐