【中工开发者】HarmonyOS APP打怪小游戏

发布于:2024-12-20 ⋅ 阅读:(9) ⋅ 点赞:(0)

项目源码:https://gitee.com/miwenwen/lottery-system/tree/dev/MonsterGame

项目概述

项目中主要设置了怪物属性,玩家属性
该项目是一个基于鸿蒙操作系统的打怪小游戏,通过简单的游戏机制与图形设计,玩家可以操控角色与怪物进行战斗。游戏设计目的是为了巩固这种学期学习的鸿蒙开发的基本技巧,包括应用的结构设计、界面设计、事件处理以及基础的动画效果。

目标与功能

本游戏的核心功能是让玩家通过控制角色击败怪物,逐步提升角色等级和技能,并完成一系列的任务。玩家可以与怪物战斗、升级角色、解锁新技能以及获取奖励等功能

游戏玩法

玩家进入游戏,点击攻击,怪物的生命数和玩家的生命数相应减少,打败怪物后,玩家的金币数会增加。当玩家攒够金币后,可以到商店里购买一些属性(包括,生命值,攻击,防御等)。玩家还可以更换不同的怪物进行游戏。在游戏过程中,玩家可以选择保存,读取,退出。
保存,把当前游戏的状态保存带存档页面。
读取,可以读取存档页面的记录,继续进行游戏。

初始数据

怪物数据

export class Monster {
  //属性
  id: number
  Name: string
  Image: Resource
  //战斗
  Health: number
  Attack: number
  Defense: number
  //金币
  Gold: number
  constructor(id: number, Name: string, Image: Resource, Health: number,
              Attack: number, Defense: number, Gold: number) {
    this.id = id
    this.Name = Name
    this.Image = Image
    this.Health = Health
    this.Attack = Attack
    this.Defense = Defense
    this.Gold = Gold
  }
}

**玩家数据**

```typescript
class Player{
  //属性
  PlayerHealth:number = 1000
  PlayerAttack:number = 10
  PlayerDefense:number = 10

  //金币
  PlayerGold:number = 0
}
const player = new Player()
export default player as Player

怪物数据列表

let monster01 = new Monster(0,"绿色史莱姆",$r('app.media.smileGreen'),35,18,1,1)
let monster02 = new Monster(1,"红色史莱姆",$r('app.media.smileRed'),45,20,1,2)
let monster03 = new Monster(2,"蝙蝠",$r('app.media.bat'),35,38,3,3)
let monster04 = new Monster(3,"初级法师",$r('app.media.mage'),60,32,8,5)
let monster05 = new Monster(4,"骷髅人",$r('app.media.boner'),50,42,6,6)

export  class DataMonster {
  MonsterList :Array<Monster> = [
    monster01,monster02,monster03,monster04,monster05
  ]
}
const dataMonster = new DataMonster()
export  default dataMonster as DataMonster

主页面UI

在这里插入图片描述

Column() {
  Blank()
  Text("魔塔(伪)").fontSize(60)
    .fontColor(Color.Orange)
  .margin({bottom:50})
  Column(){
    Text("重新开始").fontSize(35).fontColor(Color.Blue)
      .onClick(()=>{
        router.replaceUrl({
          url:"pages/GamePage"
        })
      })
    Text("继续游戏").fontSize(35).fontColor(Color.Blue)
      .onClick(()=>{
        router.replaceUrl({
          url:"pages/SavePage",
          params:{
           source:"load"
          }
        })
      })
    Text("退出游戏").fontSize(35).fontColor(Color.Blue)
  }.height("30%").width("60%")
  .opacity(0.5)
  .justifyContent(FlexAlign.SpaceEvenly)
  .backgroundColor(Color.Gray)
  Blank()
}.width("100%").height("100%")
.backgroundColor("#ff999b9f")

游戏界面

在这里插入图片描述

对战界面

Column() {
	}.width("100%").height("100%")
.backgroundColor("#ffa9acaf")
@Extend(Text) function textStyle(){
  .fontSize(20).width("100%").textAlign(TextAlign.Center)
}

怪物图案和属性
在这里插入图片描述

//怪物信息
  @State monsterId: number = 0
  @State monsterHealth: number = 0


//......
Row() {
  Column() {
    Row() {
      Text("怪物:").fontSize(20)    Text(dataMonster.MonsterList[this.monsterId].Name.toString()).textStyle()
    }.width("80%")
    Row() {
      Text("生命:").fontSize(20)
      Text(this.monsterHealth.toString())
        .textStyle()
    }.width("80%")
    Row() {
      Text("攻击:").fontSize(20)   Text(dataMonster
      .MonsterList[this.monsterId].Attack.toString())
        .textStyle()
    }.width("80%")
    Row() {
      Text("防御:").fontSize(20)      Text(dataMonster
      .MonsterList[this.monsterId].Defense.toString())
        .textStyle()
    }.width("80%")
  }
  .width(200)
  .height("90%")
  .justifyContent(FlexAlign.SpaceEvenly)
  .backgroundColor(Color.Gray)
  .opacity(0.5)
}.height("120").width("90%")
.justifyContent(FlexAlign.End)
Row() {
 Image(dataMonster.MonsterList[this.monsterId].Image).width(100).height(100)
}.width("80%")
.margin({ top: 30 })

玩家图案和操作

Blank()
Row() {
  Column() {
    Button("攻击")
      .fontSize(26)
      .onClick(() => {
        this.attackEvent()
      }).backgroundColor("")
      .fontColor(Color.Red)
    Button("更换")
      .fontSize(26).backgroundColor("")
      .fontColor(Color.Blue)
      .onClick(() => {
        this.isMonsterListShow = Visibility.Visible
      })
    Button("商店")
      .fontColor(Color.Green)
      .fontSize(26).backgroundColor("")
      .onClick(()=>{
        this.isShopShow =Visibility.Visible
      })
  }.width("50%").height("100%")

  Image($r('app.media.player')).width(120).height(120)
}.width("80%").height(120).justifyContent(FlexAlign.SpaceBetween)

玩家属性及系统

//玩家信息
  @State playerHealth: number = 0
  @State playerAttack: number = 0
  @State playerDefense: number = 0
  @State playerGold: number = 0
  @State playerHited: number = 0
//......
Row() {
  Column() {
    Row() {
      Text("生命:").fontSize(20)
      Text(this.playerHealth.toString())
        .textStyle()
    }.width("90%")
    Row() {
      Text("攻击:").fontSize(20)
      Text(this.playerAttack.toString())
        .textStyle()
    }.width("90%")
    Row() {
      Text("防御:").fontSize(20)
      Text(this.playerDefense.toString())
        .textStyle()
    }.width("90%")
    Row() {
      Text("金币:").fontSize(20)
      Text(this.playerGold.toString())
        .textStyle()
    }.width("90%")
  }
  .height("80%")
  .width("60%")
  .justifyContent(FlexAlign.SpaceAround)
  .backgroundColor(Color.Gray)
  .opacity(0.5)

  Column() {
    Button("保存").backgroundColor("")
      .onClick(()=>{
        this.saveRouter()
      })
    Button("读取").backgroundColor("")
      .onClick(()=>{
        this.loadRouter()
      })
    Button("退出").backgroundColor("")
  }.height("80%")
  .width("40%")
  .justifyContent(FlexAlign.SpaceEvenly)
  .alignItems(HorizontalAlign.End)

}.width("90%").height(200)

控制按钮
攻击逻辑

//攻击事件
attackEvent() {
  let hurt: number = this.playerAttack - dataMonster.MonsterList[this.monsterId].Defense
  if (hurt>=this.monsterHealth){
    this.monsterHealth = dataMonster.MonsterList[this.monsterId].Health
    this.playerGold += dataMonster.MonsterList[this.monsterId].Gold
    return
  }
  setTimeout(() => {
    //怪物反击
    let result: number = dataMonster.MonsterList[this.monsterId].Attack - this.playerDefense
    if (result < 0) {
      return
    }
    this.playerHealth -= result
    if (this.playerHealth <= 0) {
      console.log("游戏结束")
    }
    return
  }, 500)
  //玩家攻击怪物
  if (hurt > 0) {
    this.monsterHealth -= hurt
  }
}

攻击效果

@State playerHited: number = 0
  @State monsterHited: number = 0
//....

attackEvent() {
  //攻击效果
  animateTo({
    duration: 100,
    curve:Curve.Linear,
    iterations: 1,
    onFinish: () => {
      this.monsterHited = 0
    }
  }, () => {
    this.monsterHited = 0.8
  })
//......
  setTimeout(() => {
    //怪物反击
    //攻击效果
    animateTo({
      duration: 100,
      curve:Curve.Linear,
      iterations: 1,
      onFinish: () => {
        this.playerHited = 0
      }
    }, () => {
      this.playerHited = 0.8
    })
  }, 200)
}
//......
          Stack(){                 Image(dataMonster.MonsterList[this.monsterId].Image)
              .width(100).height(100)            Image($r('app.media.hit')).width(60)
              .height(60).opacity(this.monsterHited)
          }.width(100).height(100)


          Stack(){            Image($r('app.media.player')).width(120).height(120)
            Image($r('app.media.hit')).width(60).height(60).opacity(this.playerHited)
          }.width(120).height(120)

怪物列表

//怪物列表
Column() {
  Row() {
    Text("头像").width("20%").textAlign(TextAlign.Center).fontSize(20)
    Text("名字").width("20%").textAlign(TextAlign.Center).fontSize(20)
    Text("生命").width("15%").fontSize(20)
    Text("攻击").width("15%").fontSize(20)
    Text("防御").width("15%").fontSize(20)
    Text("金币").width("15%").fontSize(20)
  }.width("100%")
  .justifyContent(FlexAlign.SpaceEvenly)
  .margin({ top: 20 })
  .backgroundColor(Color.Gray)

  List() {
    ForEach(dataMonster.MonsterList, (monster: Monster) => {
      ListItem() {
        Row() {
          Image(monster.Image).width("20%")
          Text(monster.Name).width("20%")
              .textAlign(TextAlign.Center).fontSize(20)
          Text(monster.Health.toString())
              .width("15%").fontSize(20)
          Text(monster.Attack.toString())
              .width("15%").fontSize(20)
          Text(monster.Defense.toString())
              .width("15%").fontSize(20)
          Text(monster.Gold.toString())
              .width("15%").fontSize(20)
        }.width("100%").height(120)
        .justifyContent(FlexAlign.SpaceEvenly)
        .onClick(() => {
          this.monsterId = monster.id
          this.monsterHealth = monster.Health
          this.isMonsterListShow = Visibility.Hidden
        })
      }
    })
  }
}
.width("100%")
.height("100%")
.visibility(this.isMonsterListShow)
.backgroundColor(Color.Gray)
.opacity(0.8)

商店
在这里插入图片描述

Column() {
  Text("商店").fontSize(36).fontWeight(FontWeight.Bold)
    .width("100%").textAlign(TextAlign.Center)
    .margin({top:20,bottom:30}).fontColor(Color.Blue)
  Text("你可以花费10金币,可以任意提升以下属性")
    .fontSize(24).width("90%").textAlign(TextAlign.Center)
    .margin({bottom:30}).fontColor(Color.Blue)
  Text("生命值+400").fontSize(26).width("80%").textAlign(TextAlign.Center)
    .margin({top:20}).fontColor(Color.Blue)
    .onClick(()=>{
      if (this.playerGold<10){
        return
      }
      this.playerGold-=10
      this.playerHealth+=400
    })
  Text("攻击+2").fontSize(26).width("80%").textAlign(TextAlign.Center)
    .margin({top:20}).fontColor(Color.Blue)
    .onClick(()=>{
      if (this.playerGold<10){
        return
      }
      this.playerGold-=10
      this.playerAttack+=2
    })
  Text("防御+4").fontSize(26).width("80%").textAlign(TextAlign.Center)
    .margin({top:20}).fontColor(Color.Blue)
    .onClick(()=>{
      if (this.playerGold<10){
        return
      }
      this.playerGold-=10
      this.playerDefense+=4
    })
  Text("返回").fontSize(26).width("80%").textAlign(TextAlign.Center)
    .margin({top:40}).fontColor(Color.Blue)
    .onClick(()=>{
      this.isShopShow = Visibility.Hidden
    })

}
.width("80%")
.height("70%")
.backgroundColor(Color.Gray)
.opacity(0.9)
.visibility(this.isShopShow)

数据持久化

preferences工具类

创建工具类

private pref: preferences.Preferences
//获取Preferences 实例
createPreferences(context: Context) {
  preferences.getPreferences(context, 'myPreference')
    .then((object) => {
      this.pref = object
      console.log("创建Preference 实例成功")
    })
    .catch((error) => {
      console.log("创建Preference 实例失败")
    })
}

写入Preference 实例

//写入Preference 实例
async writePreferenceValue(key: string, value: preferences.ValueType) {
  if (!this.pref) {
    return
  }
  try {
    await this.pref.put(key, value)
    await this.pref.flush()
  } catch (e) {
    console.log("数据写入失败")
  }
}

读取数据

//读取数据
async readPreferenceValue<T extends preferences.ValueType>(key: string, defaultValue: preferences.ValueType) {
  let value0 :preferences.ValueType
  if (!this.pref) {
    return
  }
  try {
    let value = await this.pref.get(key, defaultValue) as T
    value0 = value
  } catch (e) {
    console.log(e)
  }
  return value0
}

EntryAbility页面的onCreate方法传入参数

onCreate(want, launchParam) {
  preferenceUtil.createPreferences(this.context)
  hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
}

在存档页面使用preferences工具类进行数据初始化

@State SaveList: Array<number> = [0, 1, 2, 3, 4, 5]
  StoredList: Array<number[]> = []


  aboutToAppear() {
    this.setStoredList()
  }

//异步初始化
async setStoredList() {
  let promises = [];
  for (let i = 0; i < 6; i++) {
    promises.push(this.getStoredDate(i.toString()));
  }
  this.StoredList = await Promise.all(promises);
}

async getStoredDate(number: string) {
  let stores = await preferenceUtil.readPreferenceValue(number, 0) as number[];
  return stores;
}

存储页面构建

Column() {
  Text("保存列表").fontSize(26).height(40)
  List() {
    ForEach(this.SaveList, (list: number) => {
      ListItem() {
        Row() {
          SaveView({ index: list, Stored: this.StoredList[list] })
        }
        .width("100%")
        .height(130)
        .margin({ top: 10 })
        .backgroundColor(Color.Gray)
        .opacity(0.8)
        .onClick(() => {
        })
      }
    })
  }.width("100%").height("90%")
}.width("100%").height("100%")

自定义组件构建

 index:number
  PlayerHealth:number
  Stored:number[]
//......
Row(){
  Text("存档"+(this.index+1)).fontSize(26).width("30%")
  Column(){
    Row(){
      Text("生命").width("25%")
      Text("攻击").width("25%")
      Text("防御").width("25%")
      Text("金币").width("25%")
    }.width("70%").justifyContent(FlexAlign.SpaceEvenly)
    Row(){
      if (this.Stored){
        Text(this.Stored[0].toString()).width("25%")
        Text(this.Stored[1].toString()).width("25%")
        Text(this.Stored[2].toString()).width("25%")
        Text(this.Stored[3].toString()).width("25%")
      }
    }.width("70%").height("90%")
    .justifyContent(FlexAlign.SpaceEvenly)
  }.justifyContent(FlexAlign.SpaceBetween)
}.width("100%").height(130)

数据跳转

保存跳转

 stored :number[]
//....
saveRouter(){
  let storage:number[] = [this.playerHealth,this.playerAttack,   this.playerDefense,this.playerGold,
 this.monsterId,this.monsterHealth]
  router.pushUrl({
    url:"pages/SavePage",
    params:{
      storage:storage,
      source:"save"
    }
  })
}
//......
Button("保存").backgroundColor("")
              .onClick(()=>{
                this.saveRouter()
              })

数据接收

stored: number[]
  source: "save" | "load"

onPageShow() {
  let params = router.getParams()
  this.stored = params['storage'] as number[]
  this.source = params["source"]
}

数据保存

//保存数据
async saveStoredDate(i: string) {
  await preferenceUtil.writePreferenceValue(i, this.stored)
  router.back({
    url: "pages/GamePage",
    params: {
      stored: this.stored
    }
  })
}

页面回调

onPageShow() {
  let params = router.getParams()
  if (!params) {
    this.playerHealth = player.PlayerHealth
    this.playerAttack = player.PlayerAttack
    this.playerDefense = player.PlayerDefense
    this.playerGold = player.PlayerGold
    this.monsterHealth = dataMonster.MonsterList[this.monsterId].Health
    return
  }
  // this.stored= params['storage'] as number[]
  this.playerHealth = params['stored'][0]
  this.playerAttack = params['stored'][1]
  this.playerDefense = params['stored'][2]
  this.playerGold = params['stored'][3]
  this.monsterId = params['stored'][4]
  this.monsterHealth = params['stored'][5]
}

调用方法

.onClick(() => {
  if (this.source === "save") {
    //保存数据
    this.saveStoredDate(list.toString())
  } 
})

读取跳转

//读取跳转
loadRouter(){
  router.replaceUrl({
    url:"pages/SavePage",
    params:{
      source:"load"
    }
  })
}

加载数据

//加载数据
loadStoredDate(i: number) {
  let stored0 = this.StoredList[i]
  if (!stored0) {
    return
  }
  router.replaceUrl({
    url: "pages/GamePage",
    params: {
      stored: stored0
    }
  })
}

判断调用

.onClick(() => {
  if (this.source === "save") {
    //保存数据
    this.saveStoredDate(list.toString())
  } else {
    //加载数据
    this.loadStoredDate(list)
  }
})