当前位置: 首页 > news >正文

纯血Harmony NETX 5小游戏实践:贪吃蛇(附源文件)

一、项目缘起:对鸿蒙应用开发的探索

在鸿蒙系统生态逐渐丰富的当下,我一直想尝试开发一款简单又有趣的应用,以此深入了解鸿蒙应用开发的流程与特性。贪吃蛇作为经典游戏,规则易懂、逻辑清晰,非常适合用来实践。于是,基于鸿蒙的ArkTS语言,开启了这款“贪吃蛇大冒险”小游戏的开发之旅。

二、核心逻辑拆解:构建游戏的“大脑”

(一)数据结构与初始状态

首先定义SnakeSegment接口,用于描述蛇的身体片段和食物的坐标信息,包含xy两个数值属性。游戏初始时,蛇的主体snake是一个包含单个片段(初始位置{x: 150, y: 150} )的数组;食物food通过generateFood方法随机生成,其坐标范围基于350x350的游戏区域,确保在合理范围内;方向控制directionR初始为right,游戏状态gameOverfalse,分数score从0开始累积 。

interface SnakeSegment {x: number;y: number;
}
@Preview
@Component
export struct play_4 {@State snake: Array<SnakeSegment> = [{x: 150, y: 150}];@State food: SnakeSegment = this.generateFood();@State directionR: string = 'right';@State gameOver: boolean = false;@State score: number = 0;generateFood(): SnakeSegment {const x = Math.floor(Math.random() * 35) * 10; const y = Math.floor(Math.random() * 35) * 10;return {x, y};}// 后续方法...
}

(二)蛇的移动与成长机制

updateSnakePosition方法是蛇移动逻辑的核心。先根据当前方向directionR计算新蛇头的坐标:向上则headY减10,向下则加10,向左headX减10,向右则加10 。创建新蛇头后,判断是否吃到食物:若新蛇头坐标与食物坐标重合,分数增加10分,重新生成食物,且蛇身增长(将新蛇头添加到数组开头);若未吃到食物,蛇身移动(新蛇头添加到开头,同时移除最后一个片段 )。最后调用checkCollision方法检查碰撞情况。

updateSnakePosition(): void {let headX = this.snake[0].x;let headY = this.snake[0].y;switch (this.directionR) {case 'up':headY -= 10;break;case 'down':headY += 10;break;case 'left':headX -= 10;break;case 'right':headX += 10;break;}const newHead: SnakeSegment = {x: headX, y: headY};if (headX === this.food.x && headY === this.food.y) {this.score += 10;this.food = this.generateFood();this.snake = [newHead, ...this.snake];} else {this.snake = [newHead, ...this.snake.slice(0, -1)];}this.checkCollision(headX, headY);
}

(三)碰撞检测:游戏结束的判定

checkCollision方法负责判断游戏是否结束。一方面检查蛇头是否撞墙,即坐标是否超出350x350的游戏区域范围(headX < 0 || headX >= 350 || headY < 0 || headY >= 350 );另一方面检查是否撞到自己,通过遍历蛇身(从第二个片段开始),判断蛇头坐标是否与蛇身某片段坐标重合。若满足任一碰撞条件,将gameOver设为true

checkCollision(headX: number, headY: number): void {if (headX < 0 || headX >= 350 || headY < 0 || headY >= 350) {this.gameOver = true;}for (let i = 1; i < this.snake.length; i++) {if (this.snake[i].x === headX && this.snake[i].y === headY) {this.gameOver = true;break;}}
}

(四)游戏循环与重置

gameLoop方法实现游戏的持续运行,利用setTimeout模拟循环:若游戏未结束(!this.gameOver ),调用updateSnakePosition更新蛇的位置,然后递归调用自身,设置200毫秒的间隔控制游戏速度 。resetGame方法用于重置游戏状态,将蛇、食物、方向、游戏状态、分数恢复到初始值,并重新启动游戏循环。

gameLoop(): void {if (!this.gameOver) {this.updateSnakePosition();setTimeout(() => {this.gameLoop();}, 200); }
}resetGame(): void {this.snake = [{x: 150, y: 150}];this.food = this.generateFood();this.directionR = 'right';this.gameOver = false;this.score = 0;this.gameLoop();
}

三、界面搭建:给游戏穿上“外衣”

(一)整体布局框架

通过Column组件构建垂直布局的页面结构,设置背景颜色、内边距等样式,确保界面美观且有良好的布局层次。标题“贪吃蛇大冒险”以较大的字体、特定颜色和加粗样式展示,游戏结束时显示“游戏结束!”的提示文本 。

build() {Column() {Text("贪吃蛇大冒险").fontSize(28).fontColor("#4A90E2").fontWeight(FontWeight.Bold).margin({ top: 20 })if (this.gameOver) {Text("游戏结束!").fontSize(24).fontColor("#FF4757").margin({ top: 10 })}// 游戏区域、控制面板、分数与重启按钮等组件...}.width('100%').height('100%').backgroundColor("#F0F8FF").padding({ left: 10, right: 10, top: 10, bottom: 20 })
}

(二)游戏区域设计

Stack组件作为游戏区域的容器,背景设置为特定颜色和边框样式。内部通过ForEach遍历蛇的身体片段,用Text组件(临时模拟,实际可替换为图片)展示蛇身;同样用Text组件展示食物(以苹果emoji为例),并通过zIndex控制层级,确保蛇和食物在背景之上可见 。onAppear生命周期钩子在组件显示时启动游戏循环。

Stack() {ForEach(this.snake, (segment: SnakeSegment) => {Text("蛇").width(10).height(10).position({ x: segment.x, y: segment.y }).zIndex(1) })Text('🍎').width(10).height(10).position({ x: this.food.x, y: this.food.y }).zIndex(1) 
}
.backgroundColor("#ffb0d2fc")
.width('350')
.height('350')
.borderWidth(3)
.borderColor("#6CDBD3")
.borderStyle(BorderStyle.Solid)
.onAppear(() => {this.gameLoop();
})

(三)控制面板与交互

ColumnRow组件搭建方向控制面板,四个方向按钮(上、下、左、右 )通过Image组件(需确保图片资源存在,实际开发要替换正确路径 )展示,点击事件中通过判断当前方向,防止蛇反向移动(如向上时不能直接向下 )。按钮设置了背景颜色、圆角等样式,提升交互体验 。

Column() {Row({ space: 20 }) {Image($r("app.media.icon_up")).width(20).height(20).onClick(() => {if (this.directionR !== 'down') {this.directionR = 'up';}}).width(50).height(50).borderRadius(25).backgroundColor("#FFE4B5")Image($r("app.media.icon_down")).width(20).height(20).onClick(() => {if (this.directionR !== 'up') {this.directionR = 'down';}}).width(50).height(50).borderRadius(25).backgroundColor("#FFE4B5")}Row({ space: 20 }) {Image($r("app.media.icon_left")).width(20).height(20).onClick(() => {if (this.directionR !== 'right') {this.directionR = 'left';}}).width(50).height(50).borderRadius(25).backgroundColor("#FFE4B5")Image($r("app.media.icon_right")).width(20).height(20).onClick(() => {if (this.directionR !== 'left') {this.directionR = 'right';}}).width(50).height(50).borderRadius(25).backgroundColor("#FFE4B5")}
}
.margin({ top: 10, bottom: 10 })

(四)分数与重启功能

Row组件中展示分数信息,游戏结束时显示“重新开始”按钮,点击调用resetGame方法重置游戏。按钮设置了颜色、圆角等样式,方便玩家操作 。

Row() {Text(`得分: ${this.score}`).fontSize(20).fontColor("#2ECC71").fontWeight(FontWeight.Bold)if (this.gameOver) {Text("🔄 重新开始").fontSize(16).fontColor("#FFFFFF").fontWeight(FontWeight.Bold).onClick(() => {this.resetGame();}).width(120).height(40).borderRadius(20).backgroundColor("#3498DB").padding({ left: 10, right: 10 })}
}
.justifyContent(FlexAlign.SpaceEvenly)
.margin({ top: 5, bottom: 15 })

四、附源文件

interface SnakeSegment {x: number;y: number;
}
@Preview
@Component
export struct play_4 {// 蛇的主体@State snake: Array<SnakeSegment> = [{x: 150, y: 150}];// 食物位置@State food: SnakeSegment = this.generateFood();// 方向控制 (up/down/left/right)@State directionR: string = 'right';// 游戏状态@State gameOver: boolean = false;// 分数@State score: number = 0;// 生成食物generateFood(): SnakeSegment {const x = Math.floor(Math.random() * 35) * 10; // 根据游戏区域为 350x350const y = Math.floor(Math.random() * 35) * 10;return {x, y};}updateSnakePosition(): void {// 计算新的蛇头位置let headX = this.snake[0].x;let headY = this.snake[0].y;switch (this.directionR) {case 'up':headY -= 10;break;case 'down':headY += 10;break;case 'left':headX -= 10;break;case 'right':headX += 10;break;}// 创建新的蛇头const newHead: SnakeSegment = {x: headX, y: headY};// 检查是否吃到食物if (headX === this.food.x && headY === this.food.y) {// 增加分数this.score += 10;// 生成新食物this.food = this.generateFood();// 蛇身增长this.snake = [newHead, ...this.snake];} else {// 蛇身移动this.snake = [newHead, ...this.snake.slice(0, -1)];}// 检查碰撞this.checkCollision(headX, headY);}checkCollision(headX: number, headY: number): void {// 检查是否撞墙if (headX < 0 || headX >= 350 || headY < 0 || headY >= 350) {this.gameOver = true;}// 检查是否撞到自己for (let i = 1; i < this.snake.length; i++) {if (this.snake[i].x === headX && this.snake[i].y === headY) {this.gameOver = true;break;}}}// 重置游戏状态resetGame(): void {this.snake = [{x: 150, y: 150}];this.food = this.generateFood();this.directionR = 'right';this.gameOver = false;this.score = 0;this.gameLoop();}build() {Column() {// 游戏标题和说明Text("贪吃蛇大冒险").fontSize(28).fontColor("#4A90E2").fontWeight(FontWeight.Bold).margin({ top: 20 })if (this.gameOver) {Text("游戏结束!").fontSize(24).fontColor("#FF4757").margin({ top: 10 })}// 游戏区域Stack() {// 蛇的身体ForEach(this.snake, (segment: SnakeSegment) => {Text("蛇").width(10).height(10).position({ x: segment.x, y: segment.y }).zIndex(1) // 确保蛇在食物之上})// 食物Text('🍎').width(10).height(10).position({ x: this.food.x, y: this.food.y }).zIndex(1) // 确保食物可见}.backgroundColor("#ffb0d2fc").width('350').height('350').borderWidth(3).borderColor("#6CDBD3").borderStyle(BorderStyle.Solid).onAppear(() => {// 开始游戏循环this.gameLoop();})// 控制面板Column() {// 方向按钮布局Row({ space: 20 }) {// 上按钮Image($r("app.media.icon_up")).width(20).height(20).onClick(() => {if (this.directionR !== 'down') {this.directionR = 'up';}}).width(50).height(50).borderRadius(25).backgroundColor("#FFE4B5")// 下按钮Image($r("app.media.icon_down")).width(20).height(20).onClick(() => {if (this.directionR !== 'up') {this.directionR = 'down';}}).width(50).height(50).borderRadius(25).backgroundColor("#FFE4B5")}// 左右按钮行Row({ space: 20 }) {// 左按钮Image($r("app.media.icon_left")).width(20).height(20).onClick(() => {if (this.directionR !== 'right') {this.directionR = 'left';}}).width(50).height(50).borderRadius(25).backgroundColor("#FFE4B5")// 右按钮Image($r("app.media.icon_right")).width(20).height(20).onClick(() => {if (this.directionR !== 'left') {this.directionR = 'right';}}).width(50).height(50).borderRadius(25).backgroundColor("#FFE4B5")}}.margin({ top: 10, bottom: 10 })// 分数显示和重新开始按钮Row() {// 得分显示Text(`得分: ${this.score}`).fontSize(20).fontColor("#2ECC71").fontWeight(FontWeight.Bold)// 添加重新开始按钮if (this.gameOver) {Text("🔄 重新开始").fontSize(16).fontColor("#FFFFFF").fontWeight(FontWeight.Bold).onClick(() => {this.resetGame();}).width(120).height(40).borderRadius(20).backgroundColor("#3498DB").padding({ left: 10, right: 10 })}}.justifyContent(FlexAlign.SpaceEvenly).margin({ top: 5, bottom: 15 })}.width('100%').height('100%').backgroundColor("#F0F8FF").padding({ left: 10, right: 10, top: 10, bottom: 20 })}gameLoop(): void {// 如果游戏未结束,继续游戏循环if (!this.gameOver) {// 更新蛇的位置this.updateSnakePosition();// 设置下一帧(使用setTimeout模拟setInterval)setTimeout(() => {this.gameLoop();}, 200); // 修改了游戏速度,从500毫秒调整为200毫秒以提高响应性}}
}

相关文章:

  • Python训练营打卡 Day50
  • FreeRTOS信号量
  • @Configuration原理与实战
  • ​计算机网络原理超详解说​
  • Web后端基础:Maven基础
  • Web 毕设篇-适合小白、初级入门练手的 Spring Boot Web 毕业设计项目:智驿AI系统(前后端源码 + 数据库 sql 脚本)
  • P4 QT项目----串口助手(4.2)
  • 2025 高考:AI 都在哪些地方发挥了作用
  • crackme007
  • 电脑一段时间没用就变成登陆的界面
  • CppCon 2015 学习:Implementing class properties effectively
  • RocketMQ延迟消息机制
  • 【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表
  • 第5章 类的基本概念 笔记
  • 不变性(Immutability)模式
  • b2b企业网络营销如何用deepseek、豆包等AI平台获客 上海添力
  • switch选择语句
  • 打造多模态交互新范式|彩讯股份中标2025年中国移动和留言平台AI智能体研发项目
  • Linux内核 -- INIT_WORK 使用与注意事项
  • Win系统下的Linux系统——WSL 使用手册
  • wordpress隐藏后台登录/seo研究学院
  • 泰兴网站建设/济宁seo优化公司
  • 运城做网站哪家好/长沙seo运营
  • 网站建设合同需要缴纳印花税/查排名
  • 网站建设进程方案/关键字广告
  • 网站模版编辑器/谷歌商店下载安装