鸿蒙开发游戏(三)---大鱼吃小鱼(放置NPC)

时间:2024-02-16 10:21:15

效果图

添加了一个NPC(小红鱼),玩家控制小黄鱼

鸿蒙开发游戏(一)---大鱼吃小鱼(界面部署

鸿蒙开发游戏(二)---大鱼吃小鱼(摇杆控制)

鸿蒙开发游戏(三)---大鱼吃小鱼(放置NPC)

鸿蒙开发游戏(四)---大鱼吃小鱼(互吃升级)

鸿蒙开发游戏(五)---大鱼吃小鱼(添加音效)

鸿蒙开发游戏(六)---大鱼吃小鱼(称霸海洋)

 

前两篇文章我们做了摇杆控制小鱼移动,这篇将会添加一个NPC,让其自动在海洋里游荡,然后玩家控制吃掉它。在这之前我们想思考一些问题,

NPC如何生成?NPC有哪些属性?NPC是如何控制的?如何做到随机转方向?

这是该篇的难点,这里还用到了一些数学知识,包括sin,cos,弧度与角度的计算等,还是比较麻烦的,但是等你看到代码又会发现代码量很少,又会觉得如此简单,好了,看例子。

1、开始游戏

这里我们需要添加一个开始游戏按钮,因为只有玩家开始了游戏,我们启动一个计时器才会变得顺理成章,不然的话,上来就启动计时器会很好资源的。

export struct FishPage {
    @State isBegin: boolean = false
}

这里我们默认是false,未启动状态

build() {
  Row() {
    Stack() {
      // 背景
      Image($r("app.media.bg_fish"))

      if (this.isBegin == false) {
        Button('开始游戏')
          .backgroundColor('#36d')
          .onClick(() => {
          //这里把设置成true
            this.isBegin = true
            
          })
      } else {
          //渲染小鱼
      }
    }
}

 

2、添加NPC小鱼

我们在开始之前就已经开始思考了,NPC应该有什么属性,起始位置,方向,速度,等,这里暂时不需要等级,下篇互吃逻辑写。

//NPC
@State npcSpeed: number = 3
@State npcFishX: number = 300
@State npcFishY: number = 200
@State npcAngle: number = 0
@State intervalIdNPC_1: number = 1
@State npcSin: number = 1
@State npcCos: number = 1

npc显示需要写在else里面,也就是点击开始游戏后,isBegin=true时

if (this.isBegin == false) {
  Button('开始游戏')
    .backgroundColor('#36d')
    .onClick(() => {
      this.isBegin = true
    })
} else {
  Image($r("app.media.icon_fish_right"))
    .position({ x: this.xFish - this.fishRadius, y: this.yFish - this.fishRadius })
    .rotate({ angle: this.angle, centerX: '50%', centerY: '50%' })
    .width(40)
    .height(40)

  Image($r("app.media.icon_npc_2"))
    .position({ x: this.npcFishX - this.fishRadius, y: this.npcFishY - this.fishRadius })
    .rotate({ angle: this.npcAngle, centerX: '50%', centerY: '50%' })
    .objectFit(ImageFit.ScaleDown)
    .width(40)
    .height(40)
    
}

ok,第一个image是玩家控制的小鱼,第二个image是NPC。

 

3、NPC动起来

问题来了,NPC如何自己动起来了,这就又用到了计时器,这里如果有其他好的方法也评论区打出来哈。当玩家点击开始游戏按钮时启动计时器,

if (this.isBegin == false) {
  Button('开始游戏')
    .backgroundColor('#36d')
    .onClick(() => {
      this.isBegin = true
      clearInterval(this.intervalIdNPC_1)
      this.intervalIdNPC_1 = setInterval(() => {

        //设置小鱼的移动位置,
        this.npcFishX += this.npcSpeed * this.npcCos
        this.npcFishY += this.npcSpeed * this.npcSin

        this.npcFishX = this.getNPCBorderX(this.npcFishX)
        this.npcFishY = this.getNPCBorderY(this.npcFishY)


      }, 40)

    })
}

这里需要说一下,启动一个计时器,内部是不断地计算npc的位置,getNPCBorderX这个是防止走出屏幕的宽度和高度设置的,当触碰到边框要改变方向。难点也在这

getNPCBorderX(x: number) {
  if (x <= this.fishRadius) {
    x = this.fishRadius + 10
    this.getRandom()
  }
  if (x > this.screenWidth - this.fishRadius) {
    x = this.screenWidth - this.fishRadius - 15
    this.getRandom()
  }
  return x
}

getNPCBorderY(y: number) {
  if (y <= this.fishRadius) {
    y = this.fishRadius + 10
    this.getRandom()
  }
  if (y > this.screenHeight - this.fishRadius) {
    y = this.screenHeight - this.fishRadius - 10
    this.getRandom()
  }
  return y
}

那一个x方向说吧,当x<=小鱼的半径时说明,小鱼已经贴左边了,大于屏幕宽度时贴右边了,这时我们要改变方向,getRandom()就是改变方向用的,至于x=this.fishRadius +10 这个主要是当贴边后,小鱼还在走,就会触发多次if语句,生成多次方向,也就是说会出现抖动现象,感兴趣去掉赋值可以试试。

4、NPC方向生成

这一块是最麻烦的,

弧度 = 角度 * π / 180
角度 = 弧度 * 180 / π

我们认识到的Math.sin,cos,tan等一般都是传入一个弧度,而不是角度(我传入角度老是有问题,这里传入弧度就解决了)

/*随机获取一个角度*/
getRandom() {
  this.npcAngle= this.selectFrom(-179,180)
  // let angle = Math.random()+Math.random()+Math.random()
  // this.npcAngle = angle * 180 / Math.PI
  //这是是求弧度,弧度 = 角度 * π / 180

  this.npcSin = Math.sin(this.npcAngle * Math.PI /180)
  this.npcCos = Math.cos(this.npcAngle * Math.PI /180)

  console.log("角度"+this.npcAngle)
}

selectFrom(startNumber, endNumber) {
  let choice = endNumber - startNumber + 1;
  return Math.floor(Math.random() * choice + startNumber)
}

首先我们需要随机生成一个角度,比如说90度,50度,-120度等

角度是作为鱼头方向用的,我们按照speed速度去计算该方向的值

那么x轴上的值就是x = speed * cos值,如下

this.intervalIdNPC_1 = setInterval(() => {

  //设置小鱼的移动位置,
  this.npcFishX += this.npcSpeed * this.npcCos
  this.npcFishY += this.npcSpeed * this.npcSin

  this.npcFishX = this.getNPCBorderX(this.npcFishX)
  this.npcFishY = this.getNPCBorderY(this.npcFishY)


}, 40)

 

5、关闭计时器
onPageHide() {
  clearInterval(this.intervalIdNPC_1)
}

记住页面消失时,或者游戏结束时需要关闭计时器

好了,到这NPC防止就完成了。