Go Ebiten小游戏开发:贪吃蛇

Snake

贪吃蛇是一款经典的小游戏,玩法简单却充满乐趣。本文将介绍如何使用 Go 语言和 Ebiten 游戏引擎开发一个简单的贪吃蛇游戏。通过这个项目,你可以学习到游戏开发的基本流程、Ebiten 的使用方法以及如何用 Go 实现游戏逻辑。

项目简介

贪吃蛇的核心玩法是控制一条蛇在网格中移动,吃掉随机生成的食物,每吃一个食物蛇身会变长,同时得分增加。如果蛇撞到墙壁或自己的身体,游戏结束。

本项目使用 Go 语言和 Ebiten 游戏引擎实现。Ebiten 是一个轻量级的 2D 游戏引擎,非常适合开发小游戏。

开发环境

  • Go 版本:1.20+
  • Ebiten 版本:v2.5.0+
  • 开发工具:VS Code 或 GoLand

安装 Ebiten:

go mod init snake
go get -u github.com/hajimehoshi/ebiten/v2

游戏设计

游戏元素

  • :由头部和身体组成,头部控制移动方向,身体跟随头部移动。
  • 食物:随机出现在网格中,蛇吃到食物后身体变长。
  • 网格:游戏区域被划分为固定大小的网格,蛇和食物都位于网格中。

游戏规则

  • 蛇每移动一格,身体跟随头部移动。
  • 吃到食物后,蛇身变长,食物重新生成。
  • 如果蛇撞到墙壁或自己的身体,游戏结束。

实现细节

游戏状态

游戏的核心状态由 Game 结构体管理,包括蛇的位置、食物位置、当前方向、分数等。

type Game struct {Head      Pos     // 蛇头位置Body      []Pos   // 蛇身位置列表Food      Pos     // 食物位置Dir       int     // 当前移动方向Score     int     // 当前分数GameOver  bool    // 游戏是否结束Paused    bool    // 游戏是否暂停TickCount int     // 更新计数器
}

游戏循环

Ebiten 的游戏循环由 UpdateDraw 方法实现:

  • Update:处理游戏逻辑更新,如蛇的移动、碰撞检测、输入处理等。
  • Draw:绘制游戏画面,包括蛇、食物和分数。

蛇的移动

蛇的移动通过更新头部位置,并将身体各部分依次移动到前一个部分的位置实现。

func (g *Game) Next() {// 移动蛇身for i := len(g.Body) - 1; i > 0; i-- {g.Body[i] = g.Body[i-1]}g.Body[0] = g.Head// 移动蛇头g.Head.X += Direction[g.Dir].Xg.Head.Y += Direction[g.Dir].Y
}

碰撞检测

碰撞检测分为两种情况:

  • 撞墙:蛇头超出网格范围。
  • 撞自己:蛇头与身体任何部分重合。
func (g *Game) IsDead() bool {// 检查是否撞墙if g.Head.X < 0 || g.Head.X >= GridSize || g.Head.Y < 0 || g.Head.Y >= GridSize {return true}// 检查是否撞到自己for _, pos := range g.Body {if g.Head == pos {return true}}return false
}

食物生成

食物需要随机生成在网格中,且不能与蛇的身体重合。

func (g *Game) SpawnFood() {for {x := rand.IntN(GridSize)y := rand.IntN(GridSize)if !g.IsOccupied(x, y) {g.Food = Pos{x, y}break}}
}

输入处理

通过检测键盘输入来控制蛇的移动方向,并支持暂停和重置游戏。

func (g *Game) HandleInput() {if ebiten.IsKeyPressed(ebiten.KeyEscape) {os.Exit(0) // 按下 Esc 键退出游戏}if ebiten.IsKeyPressed(ebiten.KeyP) {g.Paused = !g.Paused // 按下 P 键切换暂停状态}if !g.Paused && !g.GameOver {// 处理方向键输入if ebiten.IsKeyPressed(ebiten.KeyLeft) && g.Dir != RIGHT {g.Dir = LEFT}if ebiten.IsKeyPressed(ebiten.KeyRight) && g.Dir != LEFT {g.Dir = RIGHT}if ebiten.IsKeyPressed(ebiten.KeyUp) && g.Dir != DOWN {g.Dir = UP}if ebiten.IsKeyPressed(ebiten.KeyDown) && g.Dir != UP {g.Dir = DOWN}}
}

运行效果

运行游戏后,你会看到一个简单的贪吃蛇界面:

  • 使用方向键控制蛇的移动。
  • 吃到食物后,蛇身变长,分数增加。
  • 如果蛇撞到墙壁或自己的身体,游戏结束,按下 R 键可以重新开始。

完整代码

package mainimport ("fmt""image/color""math/rand/v2""os""github.com/hajimehoshi/ebiten/v2""github.com/hajimehoshi/ebiten/v2/ebitenutil""github.com/hajimehoshi/ebiten/v2/vector"
)const (GridSize     int     = 40                        // 网格大小(每个格子的大小)BlockSize    float32 = 20                        // 每个格子的像素大小WindowWidth  int     = GridSize * int(BlockSize) // 窗口宽度WindowHeight int     = GridSize * int(BlockSize) // 窗口高度InitialTPS   int     = 5                         // 初始每秒更新次数(游戏速度)ScorePerFood int     = 10                        // 每吃一个食物增加的分数
)const (RIGHT = iota // 右方向DOWN         // 下方向UP           // 上方向LEFT         // 左方向
)var (HeadColor color.Color = color.NRGBA{0, 0, 255, 255}     // 蛇头颜色BodyColor color.Color = color.NRGBA{255, 255, 255, 255} // 蛇身颜色FoodColor color.Color = color.NRGBA{255, 0, 0, 255}     // 食物颜色
)// Pos 表示一个二维坐标
type Pos struct {X, Y int
}// Direction 表示四个方向的移动向量
var Direction [4]Pos = [4]Pos{{1, 0}, {0, 1}, {0, -1}, {-1, 0}}// Game 表示游戏的状态
type Game struct {Head      Pos   // 蛇头位置Body      []Pos // 蛇身位置列表Food      Pos   // 食物位置Dir       int   // 当前移动方向Score     int   // 当前分数GameOver  bool  // 游戏是否结束Paused    bool  // 游戏是否暂停TickCount int   // 更新计数器
}// Update 是游戏的主更新逻辑
func (g *Game) Update() error {if g.GameOver {// 如果游戏结束,检测是否按下 R 键来重置游戏if ebiten.IsKeyPressed(ebiten.KeyR) {g.Reset()}return nil}g.TickCount++if g.TickCount >= 60/InitialTPS {g.TickCount = 0if !g.Paused {g.Next() // 更新游戏状态}}g.HandleInput() // 处理玩家输入return nil
}// Draw 是游戏的主绘制逻辑
func (g *Game) Draw(screen *ebiten.Image) {DrawGameState(screen, g)
}// Layout 设置游戏窗口的布局
func (g *Game) Layout(outerWidth, outerHeight int) (int, int) {return WindowWidth, WindowHeight
}func main() {ebiten.SetWindowTitle("Snake")                  // 设置窗口标题ebiten.SetWindowSize(WindowWidth, WindowHeight) // 设置窗口大小game := &Game{}game.Reset() // 初始化游戏状态if err := ebiten.RunGame(game); err != nil {panic(err)}
}// DrawGameState 绘制游戏状态
func DrawGameState(screen *ebiten.Image, g *Game) {// 绘制食物vector.DrawFilledRect(screen, float32(g.Food.X)*BlockSize, float32(g.Food.Y)*BlockSize, BlockSize, BlockSize, FoodColor, true)// 绘制蛇头vector.DrawFilledRect(screen, float32(g.Head.X)*BlockSize, float32(g.Head.Y)*BlockSize, BlockSize, BlockSize, HeadColor, true)// 绘制蛇身for _, pos := range g.Body {vector.DrawFilledRect(screen, float32(pos.X)*BlockSize, float32(pos.Y)*BlockSize, BlockSize, BlockSize, BodyColor, true)}// 绘制分数scoreText := fmt.Sprintf("Score: %d", g.Score)ebitenutil.DebugPrint(screen, scoreText)// 如果游戏结束,显示游戏结束信息if g.GameOver {ebitenutil.DebugPrintAt(screen, "Game Over! Press R to restart.", WindowWidth/2-100, WindowHeight/2)}
}// Next 更新游戏状态
func (g *Game) Next() {// 检查蛇是否吃到食物if g.Head == g.Food {g.Body = append(g.Body, g.Body[len(g.Body)-1]) // 增加蛇身长度g.Score += ScorePerFood                        // 增加分数g.SpawnFood()                                  // 生成新的食物}// 移动蛇身for i := len(g.Body) - 1; i > 0; i-- {g.Body[i] = g.Body[i-1]}g.Body[0] = g.Head// 移动蛇头g.Head.X += Direction[g.Dir].Xg.Head.Y += Direction[g.Dir].Y// 检查是否碰撞if g.IsDead() {g.GameOver = true}
}// SpawnFood 生成新的食物
func (g *Game) SpawnFood() {for {x := rand.IntN(GridSize)y := rand.IntN(GridSize)if !g.IsOccupied(x, y) {g.Food = Pos{x, y}break}}
}// IsOccupied 检查某个位置是否被蛇占据
func (g *Game) IsOccupied(x, y int) bool {if g.Head.X == x && g.Head.Y == y {return true}for _, pos := range g.Body {if pos.X == x && pos.Y == y {return true}}return false
}// IsDead 检查蛇是否死亡(撞墙或撞到自己)
func (g *Game) IsDead() bool {// 检查是否撞墙if g.Head.X < 0 || g.Head.X >= GridSize || g.Head.Y < 0 || g.Head.Y >= GridSize {return true}// 检查是否撞到自己for _, pos := range g.Body {if g.Head == pos {return true}}return false
}// HandleInput 处理玩家输入
func (g *Game) HandleInput() {if ebiten.IsKeyPressed(ebiten.KeyEscape) {os.Exit(0) // 按下 Esc 键退出游戏}if ebiten.IsKeyPressed(ebiten.KeyP) {g.Paused = !g.Paused // 按下 P 键切换暂停状态}if !g.Paused && !g.GameOver {// 处理方向键输入if ebiten.IsKeyPressed(ebiten.KeyLeft) && g.Dir != RIGHT {g.Dir = LEFT}if ebiten.IsKeyPressed(ebiten.KeyRight) && g.Dir != LEFT {g.Dir = RIGHT}if ebiten.IsKeyPressed(ebiten.KeyUp) && g.Dir != DOWN {g.Dir = UP}if ebiten.IsKeyPressed(ebiten.KeyDown) && g.Dir != UP {g.Dir = DOWN}}
}// Reset 重置游戏状态
func (g *Game) Reset() {g.Head = Pos{2, 0}                                     // 初始化蛇头位置g.Body = []Pos{{1, 0}, {0, 0}}                         // 初始化蛇身g.Food = Pos{rand.IntN(GridSize), rand.IntN(GridSize)} // 初始化食物位置g.Dir = RIGHT                                          // 初始方向向右g.Score = 0                                            // 重置分数g.GameOver = false                                     // 重置游戏结束状态g.Paused = false                                       // 重置暂停状态g.TickCount = 0                                        // 重置计数器
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/2818.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ASP.NET Core - .NET 6 以上版本的入口文件

ASP.NET Core - .NET 6 以上版本的入口文件 自从.NET 6 开始&#xff0c;微软对应用的入口文件进行了调整&#xff0c;移除了 Main 方法和 Startup 文件&#xff0c;使用顶级语句的写法&#xff0c;将应用初始化的相关配置和操作全部集中在 Program.cs 文件中&#xff0c;如下&…

Chapter1:初见C#

参考书籍&#xff1a;《C#边做边学》&#xff1b; 1.初见C# 1.1 C#简介 C # {\rm C\#} C#编写了许多完成常用功能的程序放在系统中&#xff0c;把系统中包含的内容按功能分成多个部分&#xff0c;每部分放在一个命名空间中&#xff0c;导入命名空间语法格式如下&#xff1a; /…

React封装倒计时按钮

背景 在开发过程中&#xff0c;经常需要使用到倒计时的场景&#xff0c;当用户点击后&#xff0c;按钮进行倒计时&#xff0c;然后等待邮件或者短信发送&#xff0c;每次都写重复代码&#xff0c;会让代码显得臃肿&#xff0c;所以封装一个组件来减少耦合 创建一个倒计时组件…

【编译构建】用cmake编译libjpeg动态库并实现转灰度图片

先编译出libjepg动态库 1、下载libjpeg源码: https://github.com/libjpeg-turbo/libjpeg-turbo 2、编译出动态库或静态库 写一个编译脚本&#xff0c;用cmake构建。 #!/bin/bash# 定义变量 SOURCE_DIR"/home/user/libjpeg-turbo-main" BUILD_DIR"${SOURCE_…

ORB-SLAM2源码学习: Frame.cc: cv::Mat Frame::UnprojectStereo将某个特征点反投影到三维世界坐标系中

前言 这个函数是在跟踪线程中更新上一帧的函数中被调用。 1.函数声明 cv::Mat Frame::UnprojectStereo(const int &i) 2.函数定义 1.获取这个特征点的深度值。 const float z mvDepth[i];深度值由双目或 RGB-D 传感器获取。 在双目情况下&#xff0c;这个深度来自…

单片机存储器和C程序编译过程

1、 单片机存储器 只读存储器不是并列关系&#xff0c;是从ROM发展到FLASH的过程 RAM ROM 随机存储器 只读存储器 CPU直接存储和访问 只读可访问不可写 临时存数据&#xff0c;存的是CPU正在使用的数据 永久存数据&#xff0c;存的是操作系统启动程序或指令 断电易失 …

【Excel】【VBA】双列排序:坐标从Y从大到小排列之后相同Y坐标的行再对X从小到大排列

Excel VBA 双列排序 功能概述 这段VBA代码实现了Excel中的双列排序功能&#xff0c;具体是&#xff1a; 跳过前3行表头先按C列数据从大到小排序在C列值相同的情况下&#xff0c;按B列从大到小排序排序时保持整行数据的完整性 流程图 #mermaid-svg-XJERemQluZlM4K8l {font-fa…

【25考研】西南交通大学软件工程复试攻略!

一、复试内容 复试对考生的既往学业情况、外语听说交流能力、专业素质和科研创新能力&#xff0c;以及综合素质和一贯表现等进行全面考查,主要考核内容包括思想政治素质和道德品质、外语听说能力、专业素质和能力&#xff0c;综合素质及能力。考核由上机考试和面试两部分组成&a…

运行fastGPT 第四步 配置ONE API 添加模型

上次已经装好了所有的依赖和程序。 下面在网页中配置One API &#xff0c;这个是大模型的接口。配置好了之后&#xff0c;就可以配置fastGPT了。 打开 OneAPI 页面 添加模型 这里要添加具体的付费模型的API接口填进来。 可以通过ip:3001访问OneAPI后台&#xff0c;**默认账号…

硬件知识:显示器发展历程介绍

目录 一、阴极射线管显示器(CRT) 二、等离子显示器(PDP) 三、液晶显示器(LCD) 四、传统LED显示器(LED) 五、有机发光二极管显示器(OLED) 六、量子点显示器(QD) 七、MiniLED显示器(MiniLED) 八、MicroLED显示器(MicroLED) 总结 显示器作为电子设备与人…

修复5.0.0r 64位版本浏览器和一些库找不到的问题

笔者在使用5.0.0r版本64位时踩过不少坑&#xff0c;先将相关修复方法分享 浏览器无法使用 base/startup/appspawn/appdata-sandbox64.json添加沙箱配置 相关修复pr:https://gitee.com/openharmony/startup_appspawn/pulls/1854/files {"src-path" : "/system…

【Flink系列】4. Flink运行时架构

4. Flink运行时架构 4.1 系统架构 Flink运行时架构——Standalone会话模式为例 1&#xff09;作业管理器&#xff08;JobManager&#xff09; JobManager是一个Flink集群中任务管理和调度的核心&#xff0c;是控制应用执行的主进程。也就是说&#xff0c;每个应用都应该被…

IP层之分片包的整合处理

前言 在上一章节中&#xff0c;笔者就IP层的接收代码逻辑做了简单介绍&#xff0c;并对实现代码进行了逻辑梳理以及仿真测试&#xff0c;并且在上一章节中&#xff0c;就IP层的分片包问题&#xff0c;如何确定分片包是否存在已经进行了简单介绍&#xff0c;并在接收模块中&…

使用jupyter notebook没有正常打开浏览器的几种情况解决

迅速记录前期 1.下载 https://www.anaconda.com/products/individual 2.安装 直接默认安装就行 3.打开jupyter notebook 在开始菜单里面可以找到 4.遇到的问题解决 1.运行jupyter notebook&#xff0c;黑窗口自动关了 每次黑窗口迅速的加载完就自己关掉了 也没有打开新…

50.【8】BUUCTF WEB HardSql

进入靶场 随便输输 上order by ????????&#xff0c;被过滤了,继续找其他也被过滤的关键字 #&#xff0c;-- -&#xff0c;-- 都不行&#xff0c;尝试其他特殊字符后发现and&#xff0c;union&#xff0c;select&#xff0c;空格&#xff0c;都被过滤了 如下 我就不知…

中国石油大学(华东)自动评教工具(涵盖爬虫的基础知识,适合练手)

我开发了一个用于自动评教的工具&#xff0c;大家可以试着用用&#xff0c;下面是链接。 https://github.com/restrain11/auto_teachingEvaluate 可以点个星吗&#xff0c;感谢&#xff01;&#x1fae1; 以下是我在开发过程中学到的知识 以及 碰到的部分问题 目录 动态爬虫和静…

深入云电脑PC Farm技术探讨,以阿里云、华为云、ToDesk为例

&#x1f31d;引言 近年来&#xff0c;云计算技术的飞速发展为各行各业的数字化转型带来了全新机遇&#xff0c;其中云电脑作为一种虚拟化桌面解决方案&#xff0c;逐渐成为个人用户与企业的核心选择。从远程办公、在线教育到高性能计算需求&#xff0c;云电脑通过为用户提供随…

RK3568平台(音频篇)lineout无声调试

一.声音硬件框架 硬件HP_MUTE已强制拉低。 二.设备树配置 es8388_sound: es8388-sound {status = "okay";compatible = "rockchip,multicodecs-card";rockchip,card-name = "rockchip-es8388";hp-det-gpio = <&gpio1 RK_PD2 GPIO_ACT…

docker 部署 MantisBT

1. docker 安装MantisBT docker pull vimagick/mantisbt:latest 2.先运行实例&#xff0c;复制配置文件 docker run -p 8084:80 --name mantisbt -d vimagick/mantisbt:latest 3. 复制所需要配置文件到本地路径 docker cp mantisbt:/var/www/html/config/config_inc.php.…

【Linux】Mysql部署步骤

一、JDK安装配置 在home目录下执行命令&#xff1a;mkdir Jdk 1.将JDK 上传至该文件夹&#xff0c;有些终端工具可以直接上传文件&#xff0c;比如&#xff1a;MobaXterm 可以看到安装包已经上传上来了 2.直接安装 命令&#xff1a;rpm -ivh jdk-8u311-linux-x64.rpm 3.安装成…