在 Power Apps 中创建贪吃蛇游戏
去年我有幸参加了在拉斯维加斯举办的 Power Platform 大会,其中我最喜欢的演示之一是 GitHub Copilot 的演示。Copilot 用 JavaScript/HTML/CSS 实现了 90% 的蛇形效果,这非常酷,但也让我不禁思考……我能不能在 Power App 中实现它呢?😎
我玩 Power App 游戏通常是为了学习新技术,但这次纯粹是为了满足我的这个渴望。希望我能从中学到一些东西。我的要求是:
- 按下按钮时蛇会改变方向
- 这个变化发生在身体部位(所以蛇可以卷曲)
- 苹果可以增加蛇的长度
- 蛇吃掉自己的身体就会死
- 为了向诺基亚致敬,我们需要将其设计成功能手机
1. 设置
我的方法是创建一个 10 x 10 的网格图库,每个方格都代表蛇/苹果的一部分。我创建了一个集合 (colGrid) 来填充图库,并使用一个模板版本为每个新游戏重置。
应用程序启动
ClearCollect(colGridTemplate,
{id:1,snake:true,apple:false}
);
ForAll(Sequence(100,1,1),
Patch(colGridTemplate,{id:ThisRecord.Value},{
snake:false,
apple:false,
id:ThisRecord.Value
}
)
);
ClearCollect(colGrid,colGridTemplate);
网格表明它是蛇还是苹果的一部分(我们用它来在画廊上展示)。
蛇是画廊内的一个矩形组件,如果项目有蛇,则填充由 ( If(ThisItem.snake,Color.Black,RGBA(187, 221, 140, 1))
) 设置。
2. 运动
蛇将拥有自己的集合,这样我们就可以向其中添加内容并定位它。我们根据它所在的网格方格(网格项的 ID)来定位它。
开始按钮-OnSelect
ClearCollect(colSnake,[35,45,55]);
Set(viDirection,10);
这表明蛇的起始位置是方格 35,45,55
对于移动,我将使用我的老朋友计时器。每次循环都会循环遍历蛇集合并使其递增。递增值由方向决定。因此,如果蛇向上移动,则递减 10 格,向下移动 +10 格,向左移动 -1 格,向右移动 +1 格。
更新蛇后,我们首先重置棋盘,然后循环遍历蛇并更新每个相关棋盘项目的蛇值。
定时器结束
ForAll(colSnake,
If(ThisRecord.Value+viDirection>100,
Patch(colSnake,ThisRecord,{Value:ThisRecord.Value-90});
,
If(ThisRecord.Value+viDirection<1,
Patch(colSnake,ThisRecord,{Value:ThisRecord.Value+90});
,
Patch(colSnake,ThisRecord,{Value:ThisRecord.Value+viDirection});
)
)
);
ClearCollect(colGrid,colGridTemplate);
ForAll(colSnake,
Patch(colGrid,{id:ThisRecord.Value},{
snake:true
}
);
);
您还可以看到一些额外的逻辑来处理蛇的包裹(离开底部屏幕并出现在顶部)。
不幸的是,这种方法不太奏效,我们最终移动了整条蛇,而不是蛇头。
回到最初的设想,新的方案是在棋盘集合中添加一个新字段来保存方向。这样,我们就可以存储棋盘上的多个方向,但只有头部会遵循设定的方向。
我意识到的第一件事是我的蛇放错了方向(第一个项目应该是它的头🤦♂️(colSnake,[55,45,35]
)。其次,我无法在每次移动时重置棋盘,而是需要将退出的方块更新为蛇:false,我通过将蛇的最后一部分存储在变量中但移动它来做到这一点,然后修补该网格方块。所以我最终得到了:
定时器结束
//// get grid square to remove snake
Set(viClearGrid,Last(colSnake).Value);
ForAll(Sequence(CountRows(colSnake)) As item,
With(
{
snake:Index(colSnake,item.Value)
},
If(item.Value=1,
Patch(colSnake,snake,{Value:snake.Value+viDirection});
,
Patch(colSnake,snake,{Value:snake.Value+Index(colGrid,snake.Value).direction});
)
)
);
////fix over flows
ForAll(colSnake,
If(ThisRecord.Value>100,
Patch(colSnake,ThisRecord,{Value:ThisRecord.Value-90});
,
If(ThisRecord.Value<1,
Patch(colSnake,ThisRecord,{Value:ThisRecord.Value+90});
)
)
);
//// move snake
ForAll(colSnake,
If(ThisRecord.Value=Index(colSnake,2).Value Or ThisRecord.Value=Index(colSnake,1).Value,
Patch(colGrid,{id:ThisRecord.Value},{
direction:viDirection,
snake:true
}
)
,
Patch(colGrid,{id:ThisRecord.Value},{
snake:true
}
)
)
);
///set grid square to remove snake
Patch(colGrid,{id:viClearGrid},{
snake:false
}
);
对于移动蛇,我还需要将板更新到新的方向,我更新了第一和第二个项目,因为我需要留下一个方向轨迹(第一个项目),并且方向的实际变化是在最后一次转弯处,即在第一个项目移动之前(即现在的第二个项目)。
我还必须在开始时对棋盘进行不同的设置。为了让蛇移动,它所在的每个起始格子也必须有一个方向。所以我只是简单地循环遍历它,并修补相关的网格格子。
开始按钮-OnSelect
Set(viDirection,10);
ClearCollect(colSnake,[55,45,35]);
ClearCollect(colGrid,colGridTemplate);
ForAll(colSnake,
Patch(colGrid,{id:ThisRecord.Value},{
snake:true,
direction:viDirection
}
)
);
差一点就成功了,但还没完全搞定。我意识到虽然修复了顶部和底部的溢出问题,但我忘了处理侧面的问题。所以它总是会在右侧上移一行,在左侧下移一行。为了解决这个问题,我在移动蛇之前创建了一个克隆版本(colSnakePast),这样我就可以检查蛇的来源,并添加另外两个检查:
- 如果蛇在最后一列(Mod 平方 10),而之前的蛇在第一列(Mod 平方-1 10)= 从左侧溢出
- 如果蛇在第一列(Mod 平方-1 10)并且之前的蛇在最后一列(Mod 平方 10)= 从右侧溢出
ForAll(Sequence(CountRows(colSnake)) As item,
With(
{
snake:Index(colSnake,item.Value),
pastSnake:Index(colSnakePast,item.Value).Value
},
If(
snake.Value>100, Patch(colSnake,snake,{Value:snake.Value-100}),
snake.Value<0, Patch(colSnake,snake,{Value:snake.Value+100}),
Mod(snake.Value-1,10)=0 And Mod(pastSnake,10)=0,Patch(colSnake,snake,{Value:snake.Value-10}),
Mod(snake.Value,10)=0 And Mod(pastSnake-1,10)=0, Patch(colSnake,snake,{Value:snake.Value+10})
);
)
);
我还把它和😎绑在一起了
3.碰撞检测器
现在我们的蛇已经可以移动了,我们需要添加当它撞到自己时会死亡的功能。幸运的是,我们目前的设置已经基本完成,只需要过滤 pastSnake 集合,看看它是否包含头部方块。这个结果可以触发游戏结束,或者继续执行我们的移动代码。
定时器结束
If(!IsEmpty(Filter(colSnakePast,Value=First(colSnake).Value)),
Set(vbStart,false);
Notify("Game Over");
,
////fix over flows
////move snake
////set grid square to remove snake
)
4.吃苹果来成长
现在我们需要增加难度,这可以通过苹果来实现。吃苹果会增加蛇的长度,而且我们还需要随机放置苹果。
吃东西很简单,我们稍微作弊一下,实际上不会添加任何内容到蛇身上。相反,我们不会删除之前的尾巴方块。
因此,我们将“设置网格方块以移除蛇”包装在一个条件中,如果方块是苹果,则移除苹果,否则删除最后一个蛇方块:
定时器结束
With(
{
head:Index(colGrid,First(colSnake).Value).id,
tail:Last(colSnakePast).Value
},
If(Index(colGrid,head).apple,
Patch(colGrid,{id:head},
{
apple:false
}
);
Collect(colSnake,tail);
,
///set grid square to remove snake
Patch(colGrid,{id:tail},
{
snake:false
}
);
);
);
接下来我们需要添加苹果。这和蛇的添加非常类似,我们创建一个集合并循环添加它们。还有几点需要补充:
- 删除旧苹果 - 循环收集并将板方形苹果更改为 false
- 添加苹果 - 创建 3 个介于 1 到 100 之间的随机数,循环,如果不是蛇,则将棋盘方形苹果更改为 true
- 时间 - 我每 24 个周期都会这样做,但这可能是第 n 个或只是随机计划。
定时器结束
////create apples
If(Mod(viApples,24)=0,
ForAll(colApples,
Patch(colGrid,Index(colGrid,ThisRecord.Value),{apple:false})
);
ClearCollect(colApples,[RandBetween(1,100),RandBetween(1,100),RandBetween(1,100)]);
ForAll(colApples,
If(!Index(colGrid,ThisRecord.Value).snake,
Patch(colGrid,Index(colGrid,ThisRecord.Value),{apple:true})
)
)
);
- 锦上添花
现在一切正常,我们只需要添加合适的外观和一些额外的功能。
为了达到这个效果,我添加了一部诺基亚手机作为背景,然后移除了棋盘图库的内边距。我还将所有按钮设为透明,并将它们放置在背景图片上相应的按钮上方(这在某些手机上可能会导致缩放问题,但这是一个用户验收测试 (UAT) 问题 😎)。
最后,我在顶部添加了一个标签,并使用蛇形线的长度作为水平指示器。
我一直尝试从这样的创新项目中学习,但这次的乐趣大于学习。就像肌肉需要锻炼一样,我们的解决问题的能力也需要锻炼,而我发现做这种有趣的游戏是最好的锻炼方式。
这再次表明,只要有一点创造力,您几乎可以用 LowCode 做任何事情。
与往常一样,您可以在此处下载解决方案并仔细查看。
鏂囩珷鏉ユ簮锛�https://dev.to/wyattdave/creating-snake-game-in-power-apps-4n9