纯血HarmonyOS NETX 打造小游戏实践:大鱼吃小鱼(附源文件)

2025-06-29 09:46:57
109次阅读
0个评论

一、游戏整体架构设计

image.png

这款基于ArkUI的鱼类捕食游戏采用了经典的MVC架构思想,将游戏逻辑与UI渲染分离,主要包含以下核心模块:

  • 模型层(Model)Fish基类及其子类定义游戏实体
  • 视图层(View)play_6组件负责UI渲染
  • 控制层(Controller):游戏循环、交互逻辑与AI控制

这种架构使代码具有良好的可维护性和扩展性,下面我们将逐模块解析核心代码。

二、Fish基类与继承体系

1. 基类核心属性与功能
class Fish {
  size: number = 0;          // 体型决定捕食关系,数值越大越能捕食小鱼
  speed: number = 0;         // 移动速度,与体型负相关
  positionX: number = 0;     // 笛卡尔坐标系X轴位置
  positionY: number = 0;     // 笛卡尔坐标系Y轴位置
  health: number = 100;      // 生命值系统,被大鱼攻击会减少
  scoreValue: number = 0;    // 被吃掉时提供的分数,与体型正相关
  direction: number = 1;     // 移动方向(1:右,-1:左)

  constructor(
    size: number, speed: number, x: number, y: number,
    isPlayer: boolean, name: string, direction: number
  ) {
    this.size = size;
    this.speed = speed;
    this.positionX = x;
    this.positionY = y;
    // 分数价值 = 体型*1.5并取整,体现体型与价值的线性关系
    this.scoreValue = Math.floor(size * 1.5);
    this.direction = direction;
  }
}

设计亮点

  • 通过size属性构建捕食关系链,形成游戏核心玩法
  • scoreValuesize的线性关系设计,使游戏进度与视觉反馈一致
  • 统一的坐标系统为碰撞检测和移动逻辑提供基础
2. 玩家鱼PlayerFish实现
export class PlayerFish extends Fish {
  private isDragging: boolean = false; // 拖拽状态标记

  constructor(size: number, speed: number, x: number, y: number) {
    super(size, speed, x, y, true, "Player");
  }

  // 边界检测核心逻辑
  checkBoundary() {
    const displayClass = display.getDefaultDisplaySync();
    const screenWidth = displayClass.width / displayClass.densityPixels;
    const screenHeight = displayClass.height / displayClass.densityPixels;
    
    // 四个方向的边界限制,确保玩家鱼不会移出屏幕
    if (this.positionX < 0) this.positionX = 0;
    if (this.positionY < 0) this.positionY = 0;
    if (this.positionX > screenWidth - this.size * 2) {
      this.positionX = screenWidth - this.size * 2;
    }
    if (this.positionY > screenHeight - this.size * 2) {
      this.positionY = screenHeight - this.size * 2;
    }
  }
}

交互逻辑解析

  • checkBoundary方法通过获取屏幕实际尺寸,实现玩家鱼的边界限制
  • 边界计算考虑了鱼的体型大小(size * 2),避免鱼体超出屏幕显示范围
  • 拖拽控制通过isDragging状态标记与偏移量计算实现精准操作
3. NPC鱼智能行为NPCFish
class NPCFish extends Fish {
  private directionX: number = 0; // X轴移动方向分量
  private directionY: number = 0; // Y轴移动方向分量
  private lastDirectionChange: number = 0; // 上次改变方向的时间戳
  private playerFish: PlayerFish | null = null; // 玩家鱼引用

  constructor(
    size: number, speed: number, x: number, y: number,
    playerFish: PlayerFish, direction: number
  ) {
    super(size, speed, x, y, false, `NPC-${Math.random() * 1000}`, direction);
    this.playerFish = playerFish;
    this.resetDirection(); // 初始化移动方向
  }

  // 随机重置移动方向
  private resetDirection() {
    // X方向根据初始方向确定基本趋势,添加随机扰动
    this.directionX = this.direction * (Math.random() * 0.5 + 0.3);
    // Y方向完全随机,模拟鱼类自然游动
    this.directionY = (Math.random() > 0.5 ? 1 : -1) * (Math.random() * 0.3);
    this.lastDirectionChange = Date.now();
  }
}

AI行为核心

  • resetDirection方法通过随机数生成扰动向量,使NPC鱼移动更自然
  • lastDirectionChange时间戳控制方向变化频率,避免NPC鱼行为过于规律
  • 方向向量的X分量与初始方向关联,Y分量完全随机,形成"定向游动+随机摆动"的效果

三、游戏核心循环与逻辑控制

1. 游戏主循环机制
private gameLoop() {
  if (this.gameOver) return;

  // 更新所有NPC鱼状态
  this.npcFishes.forEach(npc => {
    npc.autoMove();
  });

  // 检测碰撞与生存状态
  this.checkCollisions();
  this.checkPlayerSurvival();

  // 下一帧循环(30ms约33FPS)
  setTimeout(() => {
    this.gameLoop();
  }, 30);
}

循环逻辑解析

  • 采用setTimeout实现递归循环,控制游戏更新频率
  • 先更新NPC鱼状态,再进行碰撞检测,确保逻辑顺序正确
  • gameOver状态标记可暂停循环,优化资源占用
2. 碰撞检测与捕食逻辑
// 核心碰撞检测算法
private isColliding(fish1: Fish, fish2: Fish): boolean {
  const dx = fish1.positionX - fish2.positionX;
  const dy = fish1.positionY - fish2.positionY;
  const distance = Math.sqrt(dx * dx + dy * dy);
  // 碰撞判定:两鱼中心距离小于体型半径和
  return distance < (fish1.size + fish2.size) / 2;
}

// 捕食逻辑实现
private checkCollisions() {
  const toRemove: number[] = [];

  for (let i = 0; i < this.npcFishes.length; i++) {
    const npc = this.npcFishes[i];

    // 玩家鱼捕食NPC鱼的条件:玩家鱼更大且发生碰撞
    if (this.playerFish.size > npc.size && this.isColliding(this.playerFish, npc)) {
      // 玩家鱼成长:体型增加NPC鱼体型的15%
      this.playerFish.size += npc.size * 0.15;
      // 分数增加:获取NPC鱼的scoreValue
      this.score += npc.scoreValue;
      
      // 速度调整:体型越大速度越慢,最小速度为1
      this.playerFish.speed = Math.max(1, 3 - this.playerFish.size / 20);
      
      // 标记NPC鱼为移除
      toRemove.push(i);
    }
  }

  // 从后往前移除,避免索引错误
  for (let i = toRemove.length - 1; i >= 0; i--) {
    this.npcFishes.splice(toRemove[i], 1);
  }
}

算法关键点

  • 碰撞检测采用欧几里得距离计算,时间复杂度O(n)
  • 捕食后玩家鱼的成长比例(15%)与速度衰减公式(3 - size/20)经过平衡设计
  • toRemove数组收集待移除索引,避免遍历时修改数组导致的逻辑错误
3. NPC鱼智能反应逻辑
autoMove() {
  // 定期改变方向(1000-3000ms随机间隔)
  if (Date.now() - this.lastDirectionChange > 1000 + Math.random() * 2000) {
    this.resetDirection();
  }

  // 基础移动:方向向量*速度
  this.positionX += this.directionX * this.speed;
  this.positionY += this.directionY * this.speed;

  // 边界循环机制
  if (this.positionX < -this.size * 2) {
    // 从左侧消失,右侧重新生成
    this.positionX = this.screenWidth + this.size * 2;
    this.positionY = Math.random() * (this.screenHeight - 100) + 50;
  } else if (this.positionX > this.screenWidth + this.size * 2) {
    // 从右侧消失,左侧重新生成
    this.positionX = -this.size * 2;
    this.positionY = Math.random() * (this.screenHeight - 100) + 50;
  }

  // 对玩家鱼的智能反应
  if (this.playerFish) {
    const playerDistX = this.playerFish.positionX - this.positionX;
    const playerDistY = this.playerFish.positionY - this.positionY;
    const dist = Math.sqrt(playerDistX * playerDistX + playerDistY * playerDistY);

    if (this.playerFish.size > this.size && dist < 200) {
      // 玩家鱼更大时,NPC鱼躲避(向远离玩家鱼的方向移动)
      this.directionX = -playerDistX / dist * 0.3;
      this.directionY = -playerDistY / dist * 0.3;
    } else if (this.size > this.playerFish.size && dist < 300) {
      // NPC鱼更大时,追逐玩家鱼(向玩家鱼方向移动)
      this.directionX = playerDistX / dist * 0.3;
      this.directionY = playerDistY / dist * 0.3;
    }
  }
}

AI策略解析

  • 边界循环机制实现NPC鱼从屏幕一侧消失后从另一侧出现,形成持续流动感
  • 200300作为触发躲避/追逐的距离阈值,经过游戏平衡设计
  • 向量归一化处理(playerDistX/dist)确保方向向量长度为1,避免速度突变

四、UI渲染与用户交互

1. 组件渲染核心逻辑
build() {
  Stack() {
    // 背景层
    Row().backgroundColor('#e2e9ef').width('100%').height('100%')
    
    // 信息显示层
    Text(`分数: ${this.score}`).fontSize(24).fontColor('#FFFFFF')
    Text(`生命值: ${this.playerFish.health}`)
      .backgroundColor('rgba(0, 0, 0, 0.5)')
    
    // 玩家鱼渲染(红色图标,可拖拽)
    Text('🐟')
      .fontSize(this.playerFish.size)
      .backgroundColor('#FF0000')
      .gesture(
        PanGesture()
          .onActionStart((event) => {
            // 计算拖拽偏移量,确保手势起点精准
            this.dragOffsetX = event.offsetX - (this.playerFish.positionX - (this.playerFish.size * 2) / 2);
            this.dragOffsetY = event.offsetY - (this.playerFish.positionY - (this.playerFish.size * 2) / 2);
          })
      )
    
    // NPC鱼列表渲染(蓝色图标,方向动态变化)
    ForEach(this.npcFishes, (npc) => {
      Text(npc.direction > 0 ? '🐠' : '🐟')
        .fontSize(npc.size)
        .backgroundColor('#ADD8E6')
    })
    
    // 游戏结束层(半透明遮罩)
    if (this.gameOver) {
      Column()
        .backgroundColor('rgba(0, 0, 0, 0.7)')
        .children([
          Text('游戏结束!'),
          Button('重新开始').onClick(() => {
            // 重置游戏状态
          })
        ])
    }
  }
}

UI设计关键点

  • Stack布局实现多层叠加效果,背景层、信息层、鱼群层、结束层有序排列
  • ForEach组件实现NPC鱼列表的动态渲染,每条鱼根据direction属性显示不同图标
  • 半透明遮罩(rgba(0,0,0,0.7))在游戏结束时覆盖整个屏幕,突出显示结束信息
2. 屏幕适配与动态生成
// 屏幕尺寸获取与初始化
aboutToAppear() {
  try {
    const displayClass = display.getDefaultDisplaySync();
    this.screenWidth = displayClass.width / displayClass.densityPixels;
    this.screenHeight = displayClass.height / displayClass.densityPixels;
    console.info(`屏幕尺寸: ${this.screenWidth}x${this.screenHeight}`);
  } catch (error) {
    // 异常处理:获取失败时使用默认值
    console.error("获取屏幕尺寸失败", error);
  }
  
  // 初始化8条NPC鱼
  this.initNPCFishes(8);
  // 启动游戏循环
  this.gameLoop();
  // 每1000ms生成新鱼
  setInterval(() => {
    this.spawnRandomFish();
  }, 1000);
}

// NPC鱼动态生成
private spawnRandomFish() {
  const size = Math.floor(Math.random() * 25) + 10; // 10-35的随机体型
  const speed = 3 - size / 10; // 速度与体型负相关
  
  // 随机从左侧或右侧生成
  const fromLeft = Math.random() > 0.5;
  let x = fromLeft ? -size * 2 : this.screenWidth + size * 2;
  const direction = fromLeft ? 1 : -1;
  
  // Y坐标限制在屏幕中间区域(70-430,假设屏幕高度500)
  const y = Math.floor(Math.random() * (500 - 70)) + 70;
  
  this.npcFishes.push(new NPCFish(size, speed, x, y, this.playerFish, direction));
  console.info(`生成NPC鱼: 位置(${x},${y}), 方向${direction}, 大小${size}`);
}

适配与生成逻辑

  • aboutToAppear生命周期钩子中获取屏幕尺寸,确保UI元素正确布局
  • 动态生成频率(1000ms)与初始数量(8条)经过游戏节奏设计
  • Y坐标限制在中间区域(70-430),避免NPC鱼生成在屏幕边缘

五、附:源代码

import { display } from "@kit.ArkUI";

// 优化后的Fish基类(增加生命值、分数等属性)
class Fish {
  size: number = 0;          // 体型(决定捕食关系)
  speed: number = 0;         // 移动速度
  positionX: number = 0;     // X坐标
  positionY: number = 0;     // Y坐标
  isPlayer: boolean = false; // 是否为玩家控制
  name: string = "";         // 角色标识
  health: number = 100;      // 生命值(可被大鱼攻击扣除)
  scoreValue: number = 0;    // 被吃掉时提供的分数
  direction: number = 1;     // 移动方向(1:右, -1:左)

  constructor(
    size: number,
    speed: number,
    x: number,
    y: number,
    isPlayer: boolean,
    name: string,
    direction: number = 1
  ) {
    this.size = size;
    this.speed = speed;
    this.positionX = x;
    this.positionY = y;
    this.isPlayer = isPlayer;
    this.name = name;
    this.scoreValue = Math.floor(size * 1.5); // 体型越大分数越高
    this.direction = direction;
  }
}

// 玩家鱼(增加拖拽控制逻辑)
export class PlayerFish extends Fish {
  private isDragging: boolean = false; // 是否正在拖拽

  constructor(size: number, speed: number, x: number, y: number) {
    super(size, speed, x, y, true, "Player");
  }

  // 设置拖拽状态
  setDragging(isDragging: boolean) {
    this.isDragging = isDragging;
  }

  // 获取拖拽状态
  getIsDragging(): boolean {
    return this.isDragging;
  }

  // 边界检测(优化版)
  checkBoundary() {
    let displayClass: display.Display = display.getDefaultDisplaySync();
    const screenWidth = displayClass.width / displayClass.densityPixels;
    const screenHeight = displayClass.height / displayClass.densityPixels;
    if (this.positionX < 0) this.positionX = 0;
    if (this.positionY < 0) this.positionY = 0;
    if (this.positionX > screenWidth - this.size * 2) {
      this.positionX = screenWidth - this.size * 2;
    }
    if (this.positionY > screenHeight - this.size * 2) {
      this.positionY = screenHeight - this.size * 2;
    }
  }
}

// NPC鱼(增加AI行为策略)
class NPCFish extends Fish {
  private directionX: number = 0;
  private directionY: number = 0;
  private lastDirectionChange: number = 0;
  private playerFish: PlayerFish | null = null;

  constructor(
    size: number,
    speed: number,
    x: number,
    y: number,
    playerFish: PlayerFish,
    direction: number
  ) {
    super(size, speed, x, y, false, `NPC-${Math.floor(Math.random() * 1000)}`, direction);
    this.playerFish = playerFish;
    this.resetDirection();
  }

  // 重置移动方向(随机生成)
  private resetDirection() {
    // 根据初始方向设置基础X方向
    this.directionX = this.direction * (Math.random() * 0.5 + 0.3); // 增大X方向速度范围
    // 随机Y方向
    this.directionY = (Math.random() > 0.5 ? 1 : -1) * (Math.random() * 0.3);
    this.lastDirectionChange = Date.now();
  }

  // AI自动移动(包含躲避与追逐逻辑)
  autoMove() {
    // 定期改变移动方向(模拟自然游动)
    if (Date.now() - this.lastDirectionChange > 1000 + Math.random() * 2000) {
      this.resetDirection();
    }

    // 基础移动
    this.positionX += this.directionX * this.speed;
    this.positionY += this.directionY * this.speed;

    // 边界检测(优化为从一侧消失后从另一侧出现)
    let displayClass: display.Display = display.getDefaultDisplaySync();
    const screenWidth = displayClass.width / displayClass.densityPixels;
    const screenHeight = displayClass.height / displayClass.densityPixels;

    // 确保鱼在屏幕内生成和移动
    if (this.positionX < -this.size * 2) {
      // 从左侧消失,重新出现在右侧
      this.positionX = screenWidth + this.size * 2;
      this.positionY = Math.random() * (screenHeight - 100) + 50;
    } else if (this.positionX > screenWidth + this.size * 2) {
      // 从右侧消失,重新出现在左侧
      this.positionX = -this.size * 2;
      this.positionY = Math.random() * (screenHeight - 100) + 50;
    }

    if (this.positionY < 0) {
      this.directionY *= -1;
    } else if (this.positionY > screenHeight) {
      this.directionY *= -1;
    }

    // 特殊行为:NPC鱼对玩家鱼的反应
    if (this.playerFish && this.playerFish.size > this.size) {
      // 玩家鱼更大时,NPC鱼尝试躲避
      const playerDistX = this.playerFish.positionX - this.positionX;
      const playerDistY = this.playerFish.positionY - this.positionY;
      const dist = Math.sqrt(playerDistX * playerDistX + playerDistY * playerDistY);

      if (dist < 200) { // 当玩家鱼靠近时
        // 向远离玩家鱼的方向移动
        this.directionX = -playerDistX / dist * 0.3;
        this.directionY = -playerDistY / dist * 0.3;
      }
    } else if (this.playerFish && this.size > this.playerFish.size) {
      // NPC鱼更大时,尝试追逐玩家鱼
      const playerDistX = this.playerFish.positionX - this.positionX;
      const playerDistY = this.playerFish.positionY - this.positionY;
      const dist = Math.sqrt(playerDistX * playerDistX + playerDistY * playerDistY);

      if (dist < 300) { // 当玩家鱼在范围内时
        // 向玩家鱼方向移动
        this.directionX = playerDistX / dist * 0.3;
        this.directionY = playerDistY / dist * 0.3;
      }
    }
  }
}


@Component
export struct play_6 {
  @State playerFish: PlayerFish = new PlayerFish(30, 3, 150, 300); // 玩家鱼初始位置
  @State npcFishes: Array<NPCFish> = [];
  @State score: number = 0;
  @State gameOver: boolean = false;
  @State isDragging: boolean = false; // 拖拽状态
  @State dragOffsetX: number = 0;    // 拖拽偏移量X
  @State dragOffsetY: number = 0;    // 拖拽偏移量Y
  @State screenWidth: number = 800;  // 屏幕宽度
  @State screenHeight: number = 400; // 屏幕高度

  // 初始化游戏
  aboutToAppear() {
    console.info("游戏初始化,玩家鱼位置: " + this.playerFish.positionX + ", " + this.playerFish.positionY);

    // 获取屏幕尺寸
    try {
      let displayClass: display.Display = display.getDefaultDisplaySync();
      this.screenWidth = displayClass.width / displayClass.densityPixels;
      this.screenHeight = displayClass.height / displayClass.densityPixels;
      console.info(`屏幕尺寸: ${this.screenWidth}x${this.screenHeight}`);
    } catch (error) {
      console.error("获取屏幕尺寸失败,使用默认值", error);
    }

    this.initNPCFishes(8);
    this.gameLoop(); // 启动游戏循环

    // 定时生成新鱼
    setInterval(() => {
      this.spawnRandomFish();
    }, 1000);
  }

  // 初始化NPC鱼
  private initNPCFishes(count: number) {
    for (let i = 0; i < count; i++) {
      this.spawnRandomFish();
    }
  }

  // 随机生成NPC鱼(从左右两侧)
  private spawnRandomFish() {
    const size = Math.floor(Math.random() * 25) + 10;
    const speed = 3 - size / 10;

    // 随机决定从左侧还是右侧生成
    const fromLeft = Math.random() > 0.5;
    let x: number, direction: number;

    if (fromLeft) {
      // 从左侧生成,向右移动
      x = -size * 2; // 左侧屏幕外
      direction = 1;
    } else {
      // 从右侧生成,向左移动
      x = this.screenWidth + size * 2; // 右侧屏幕外
      direction = -1;
    }

    // 确保Y坐标在屏幕中间区域
    const y = Math.floor(Math.random() * (500 - 70)) + 70;
    this.npcFishes.push(new NPCFish(size, speed, x, y, this.playerFish, direction));
    console.info(`生成NPC鱼: 位置(${x},${y}), 方向${direction}, 大小${size}`);
  }

  // 游戏主循环
  private gameLoop() {
    if (this.gameOver) return;

    // 更新NPC鱼状态
    this.npcFishes.forEach(npc => {
      npc.autoMove();
      // 调试:输出NPC鱼位置
      console.info(`NPC鱼位置: ${npc.positionX},${npc.positionY}`);
    });

    // 检查碰撞和生存状态
    this.checkCollisions();
    this.checkPlayerSurvival();

    // 下一帧
    setTimeout(() => {
      this.gameLoop();
    }, 30);
  }

  // 碰撞检测与捕食逻辑
  private checkCollisions() {
    const toRemove: number[] = [];

    for (let i = 0; i < this.npcFishes.length; i++) {
      const npc = this.npcFishes[i];

      // 玩家鱼捕食NPC鱼
      if (this.playerFish.size > npc.size && this.isColliding(this.playerFish, npc)) {
        // 玩家鱼成长
        this.playerFish.size += npc.size * 0.15;
        this.score += npc.scoreValue;

        // 更新玩家鱼速度(体型越大速度越慢)
        this.playerFish.speed = Math.max(1, 3 - this.playerFish.size / 20);

        // 标记NPC鱼为移除
        toRemove.push(i);
      }
    }

    // 从后往前移除,避免索引问题
    for (let i = toRemove.length - 1; i >= 0; i--) {
      this.npcFishes.splice(toRemove[i], 1);
    }
  }

  // 检测玩家鱼是否被吃掉
  private checkPlayerSurvival() {
    for (const npc of this.npcFishes) {
      if (npc.size > this.playerFish.size && this.isColliding(this.playerFish, npc)) {
        // 玩家鱼生命值减少
        this.playerFish.health -= 20;

        if (this.playerFish.health <= 0) {
          // 游戏结束
          this.gameOver = true;
        }
      }
    }
  }

  // 碰撞检测函数
  private isColliding(fish1: Fish, fish2: Fish): boolean {
    const dx = fish1.positionX - fish2.positionX;
    const dy = fish1.positionY - fish2.positionY;
    const distance = Math.sqrt(dx * dx + dy * dy);
    return distance < (fish1.size + fish2.size) / 2;
  }

  build() {
    Stack() {
      // 背景
      Row()
        .backgroundColor('#e2e9ef')
        .width('100%')
        .height('100%')

      // 分数显示
      Text(`分数: ${this.score}`)
        .fontSize(24)
        .fontColor('#FFFFFF')
        .position({ x: 20, y: 20 })

      // 玩家鱼(拖拽控制)
      Text('🐟')
        .fontSize(this.playerFish.size)
        .width(this.playerFish.size * 2)
        .height(this.playerFish.size * 2)
        .borderRadius(this.playerFish.size)
        .backgroundColor('#FF0000')
        .position({
          x: this.playerFish.positionX - (this.playerFish.size * 2) / 2,
          y: this.playerFish.positionY - (this.playerFish.size * 2) / 2
        })
        .gesture(
          PanGesture()
            .onActionStart((event: GestureEvent) => {
              // 开始拖拽,计算偏移量
              this.isDragging = true;
              this.dragOffsetX = event.offsetX - (this.playerFish.positionX - (this.playerFish.size * 2) / 2);
              this.dragOffsetY = event.offsetY - (this.playerFish.positionY - (this.playerFish.size * 2) / 2);
            })
            .onActionUpdate((event: GestureEvent) => {
              // 拖拽中更新位置
              if (this.isDragging) {
                this.playerFish.positionX = event.offsetX - this.dragOffsetX;
                this.playerFish.positionY = event.offsetY - this.dragOffsetY;
                this.playerFish.checkBoundary();
              }
            })
            .onActionEnd(() => {
              // 拖拽结束
              this.isDragging = false;
            })
        )

      // NPC鱼(修复显示问题)
      ForEach(this.npcFishes, (npc: NPCFish) => {
        Text(npc.direction > 0 ? '🐠' : '🐟')
          .fontSize(npc.size)
          .width(npc.size * 1.5)
          .height(npc.size * 1.5)
          .borderRadius(npc.size * 0.75)
          .backgroundColor('#ADD8E6')
          .position({
            x: npc.positionX - (npc.size * 1.5) / 2,
            y: npc.positionY - (npc.size * 1.5) / 2
          })
      })

      // 游戏结束提示
      if (this.gameOver) {
        Column() {
          Text('游戏结束!')
            .fontSize(48)
            .fontWeight(FontWeight.Bold)
            .fontColor('#FF0000')
          Text(`最终分数: ${this.score}`)
            .fontSize(32)
            .fontColor('#FFFFFF')
          Button('重新开始')
            .onClick(() => {
              this.playerFish = new PlayerFish(30, 3, 150, 300);
              this.npcFishes = [];
              this.score = 0;
              this.gameOver = false;
              this.initNPCFishes(8);
            })
        }
        .width('80%')
        .height('30%')
        .backgroundColor('rgba(0, 0, 0, 0.7)')
        .borderRadius(20)
        .justifyContent(FlexAlign.Center)
        .alignItems(HorizontalAlign.Center)
        .position({
          x: (this.screenWidth - this.screenWidth * 0.8) / 2,
          y: (this.screenHeight - this.screenHeight * 0.3) / 2
        })
      }
    }
    .width('100%')
    .height('100%')
  }
}
收藏00

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