因为有事,断更了一个月,一个月没怎么碰,已经有点忘记了,这周做点简单案例进行一下“康复训练”。
本周的案例是一个色格翻转的小游戏
如上图,点击格子后,所点击的格子及其相邻的格子会翻转(变成橙色)当全部翻转时,则通关。
这个案例的布局没有什么难度,就跳过。
整体实现逻辑
本案例的整体实现逻辑如下:
1、先进行UI设计,实现基础布局
2、定义box类,实现翻转逻辑
3、定义boxes类,用于存放有box类型组成的对象数组,并对其进行初始化。
4、定义boxView视图,通过@Observed监听box类,然后@ObjectLink的方式传入,才能实现响应式更改box的状态。(因为harmonyos中无法直接进行深度监听,所以就算我们修改了boxes中的box中的turned,也无法实现视图上的更改。)
5、实现相邻格子的翻转逻辑。
6、判断是否所有格子都已翻转,都翻转则进入下一关。
7、进入下一关后的页面渲染、更改逻辑。
8、实现三个按钮的功能。
box类
//单独的方块
@Observed
export class box{
colIndex:number
rowIndex:number
turned:boolean
index:number
constructor(colIndex:number,rowIndex:number,turned:boolean,index:number) {
this.colIndex=colIndex
this.rowIndex=rowIndex
this.turned=turned
this.index=index
}
changeTurned(){
this.turned=!this.turned
}
setBox(colIndex:number,rowIndex:number,turned:boolean,index:number) {
this.colIndex=colIndex
this.rowIndex=rowIndex
this.turned=turned
this.index=index
}
}
首先我们要给每个定义一个格子类 ,因为我们的格子需要包含一些属性和方法。
colIndex是该格子是第几列,rowIndex则是第几行,这是为了服务后面判断他周围格子一起翻转用的。turned是判断盒子是否翻转,index则是格子的编号。
constructor这个函数是类中用于初始化的。changeTurned函数用于翻转格子。setBox函数用于外部可以修改格子的值。
boxes类
import { DEFAULT } from '@ohos/hypium'
import {box} from "../viewmodel/box"
//方块集合
export class boxes{
boxes:Array<box> = []
index:number =0
different:number
constructor() {
}
init(startLine:number){
for(let i=1;i<=startLine;i++){
for(let j=1;j<=startLine;j++){
this.boxes.push(new box(i,j,false,this.index))
this.index++
}
}
}
reload(startLine:number){
//扩容
this.different = startLine*startLine - (startLine-1)*(startLine-1)
for(let i =0 ;i<this.different;i++){
this.boxes.push(new box(0,0,false,0))
}
this.index=0
for(let i=1;i<=startLine;i++){
for(let j=1;j<=startLine;j++){
this.boxes[this.index].setBox(i,j,false,this.index)
this.index++
}
}
}
reLevel(){
this.index=0
for (let i = 0; i < this.boxes.length; i++) {
this.boxes[i].turned=false
}
}
restart(){
this.index=0;
this.boxes.splice(1,this.boxes.length-1)
this.boxes[0].setBox(1,1,false,0)
}
}
const boxList = new boxes()
export default boxList as boxes
有了存放单个box的类,同时也要有存放所有格子的类,也就是我们的boxes类,他是由box类型组成的数组。
init函数用于新建boxes数组,因为我们要给每个box传入一个标记index,所以我们这里也定义一个index,从0开始,每标记一个格子index+1,这样就可以实现每个盒子都有自己的标记,便于后续的使用。
reload函数用于跳转到下一关时的初始化,第一个for循环中用于扩容,因为上一关时我们的boxes中就有一定数量的box了,所以我们只需要在push一定数量的box到boxes中即可。第二个嵌套for循环用于给扩容后的boxes进行初始化。
reLevel函数用于实现重玩本关的功能,主要就是将boxes中的所有box的turned改为false,也就是未翻转。
restart则是重新开始游戏,也就是回到第一关,那么我们则应该把boxes删除直至只剩一个box,然后重置这个box即可。
boxView视图
import {box} from "../viewmodel/box"
@Component
export struct boxView{
@ObjectLink b:box
build(){
Row(){
}.height("80%")
.width("90%")
.margin(10)
.backgroundColor(this.b.turned?Color.Orange:Color.Blue)
}
}
由这段代码我们可以看出,其实每个格子呢都是一个Row容器,然后设置了宽高背景色,翻转其实就是背景色的变换。@ObjectLink用于绑定b这个box对象。
index主页
初始化及变量
import {box} from "../viewmodel/box"
import {boxes} from "../viewmodel/boxes"
import boxList from "../viewmodel/boxes"
import { boxView } from "../view/boxView"
@Entry
@Component
struct Index {
@State startLine:number = 1
@State boxes:boxes =boxList
aboutToAppear(){
boxList.init(this.startLine)
}
首先引入所有我们需要用到的类和视图,这里还引入了一个boxList,boxList其实是boxes的一个实例对象,在boxes的最后我们导出了一个boxList,在这里引入了。
startLine是本案例中最重要的一个变量之一,它用于记录第几关,所有的格子数量的逻辑都与他有关。第一关会有一的平方个格子,第二关有二的平方个,第三关有三的平方个,以此类推。
同时在aboutToAppear生命周期钩子中实现了boxList的初始化,并将其赋值给boxes,实现初始化一个boxes实例的目的。
相邻翻转逻辑
turnOver(box:box){
//右侧
if(box.rowIndex<this.startLine){
this.boxes.boxes[box.index+1].changeTurned()
}
//左侧
if(box.rowIndex>1){
this.boxes.boxes[box.index-1].changeTurned()
}
//下方
if(box.colIndex<this.startLine){
this.boxes.boxes[box.index+this.startLine].changeTurned()
}
// 上方
if(box.colIndex>1){
this.boxes.boxes[box.index-this.startLine].changeTurned()
}
this.isAllOver()
}
这个逻辑很简单,左右就是当前index减一或加一就可找到左一个或者右一个格子。而每个格子和他的上一个或下一个则刚好差一行,就是startLine的大小。因此加减一个startLine就可得到上面一个或者下面一个。
判断是否都翻转
isAllOver(){
for(let i=0;i<this.boxes.boxes.length;i++){
if(!this.boxes.boxes[i].turned){
return
}
}
AlertDialog.show({
title:"成功过关",
message:`恭喜你成功通过第${this.startLine}关`,
confirm:{
value:"ok",
action:()=>{
}
}
})
this.startLine++
this.boxes.reload(this.startLine)
}
这里遍历整个boxes数组,如果其中有任何一个box的turned属性为false,也就是还未翻转,则直接return,若都已经翻转了,则出现弹框,表示已经通过。然后调用reload函数,也就是跳转到下一关的逻辑函数。
完整代码如下:
import {box} from "../viewmodel/box"
import {boxes} from "../viewmodel/boxes"
import boxList from "../viewmodel/boxes"
import { boxView } from "../view/boxView"
@Entry
@Component
struct Index {
@State startLine:number = 1
@State boxes:boxes =boxList
aboutToAppear(){
boxList.init(this.startLine)
}
turnOver(box:box){
//右侧
if(box.rowIndex<this.startLine){
this.boxes.boxes[box.index+1].changeTurned()
}
//左侧
if(box.rowIndex>1){
this.boxes.boxes[box.index-1].changeTurned()
}
//下方
if(box.colIndex<this.startLine){
this.boxes.boxes[box.index+this.startLine].changeTurned()
}
// 上方
if(box.colIndex>1){
this.boxes.boxes[box.index-this.startLine].changeTurned()
}
this.isAllOver()
}
isAllOver(){
for(let i=0;i<this.boxes.boxes.length;i++){
if(!this.boxes.boxes[i].turned){
return
}
}
AlertDialog.show({
title:"成功过关",
message:`恭喜你成功通过第${this.startLine}关`,
confirm:{
value:"ok",
action:()=>{
}
}
})
this.startLine++
this.boxes.reload(this.startLine)
}
build() {
Row() {
Column() {
Row(){
Text(`第${this.startLine}关`)
.fontSize(40)
.fontWeight(FontWeight.Bold)
.width("100%")
.textAlign(TextAlign.Center)
}
.width("100%")
.height("10%")
Row(){
Button("重新开始")
.onClick(()=> {
AlertDialog.show({
title: "重新开始",
message: "会把你传送到第一关",
confirm: {
value: "ok",
action: () => {
this.startLine = 1
this.boxes.restart()
}
}
})
})
Button("重玩本关")
.onClick(()=>{
AlertDialog.show({
title:"重玩本关",
message:"会把你重新传送到刚进入本关的状态",
confirm:{
value:"ok",
action:()=>{
this.boxes.reLevel()
}
}
})
})
Button("游戏介绍")
.onClick(()=>{
AlertDialog.show({
title:"游戏说明",
message:"让所有色块变成橙色",
confirm:{
value:"ok",
action:()=>{
}
}
})
})
}.width("90%")
.height("25%")
.justifyContent(FlexAlign.SpaceEvenly)
Column(){
GridRow({columns:this.startLine}){
ForEach(this.boxes.boxes,(box:box)=>{
GridCol(){
Row(){
boxView({b:box})
}
.onClick(()=>{
box.turned=!box.turned
this.turnOver(box)
})
}
.width("70%")
.height(400/this.startLine)
})
}
}.width("100%")
.height("60%")
}
.width('100%')
}
}
}
这里还有一个需要注意的地方:
在每一个方块的点击事件中,我们需要调用相邻翻转逻辑,此时我们一定要记得,先修改自己点击的这个格子的turned值。因为相邻翻转逻辑的最后会调用判断是否全部翻转的逻辑,你如果没有先修改点击格子的值,则会判定你还未修改前的值,则会出现错误的情况。