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.mzph.cn/bicheng/67969.shtml

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

相关文章

FCPX插件:100组二维卡通动漫流体线条MG动画元素包 MotionVfx – mzap

mZap 是一款由 motionVFX 公司出品的 Final Cut Pro X 模板&#xff0c;提供 100 种卡通动漫流体 MG 动画元素和标题效果。这套模板专为视频制作者设计&#xff0c;添加流畅且生动的动画效果&#xff0c;提升视频的创意表现力。 丰富预设&#xff1a;提供 100 种卡通动漫流体 M…

linux下实现U盘和sd卡的自动挂载

linux下实现U盘和sd卡的自动挂载 Chapter0 linux下实现U盘和sd卡的自动挂载 Chapter0 linux下实现U盘和sd卡的自动挂载 原文链接&#xff1a;https://blog.csdn.net/EmSoftEn/article/details/45099699 目的&#xff1a;使U盘和SD卡在Linux系统中进行插入和拔除时能自动挂载和…

Taro+react 开发第一节创建 带有redux状态管理的项目

Taro 项目基于 node&#xff0c;请确保已具备较新的 node 环境&#xff08;>16.20.0&#xff09;&#xff0c;推荐使用 node 版本管理工具 nvm 来管理 node&#xff0c;这样不仅可以很方便地切换 node 版本&#xff0c;而且全局安装时候也不用加 sudo 了。 1.安装 npm inf…

六年之约day12

今日开心&#xff1a;今天通过小红书找到了一个板友群&#xff0c;晚上约了一个男生一个女生一块玩滑板&#xff08;虽然有些冷&#xff0c;但滑着滑着就不冷了。也算是迈出了重要的一步”以板会友“。今后&#xff0c;持续社交吧&#xff0c;拒绝当社畜。 今日不开心&#xf…

ZooKeeper Java API操作

&#xff08;1&#xff09;添加依赖&#xff0c;在pom.xml文件中添加zookeeper依赖&#xff1a; &#xff08;2&#xff09;连接zookeeper服务&#xff0c;创建cn.itcast.zookeeper包&#xff0c;在该包中创建ZooKeeperDemo类&#xff0c;该类用于实现创建会话和操作ZooKeeper&…

什么是顶级思维?

在现代社会&#xff0c;我们常常听到“顶级思维”这个概念&#xff0c;但究竟什么才是顶级思维&#xff1f;它又是如何影响一个人的成功和幸福呢&#xff1f;今天&#xff0c;我们就来探讨一下顶级思维的几个关键要素&#xff0c;并分享一些实用的生活哲学。 1. 身体不适&…

【源码解析】Java NIO 包中的 ByteBuffer

文章目录 1. 前言2. ByteBuffer 概述3. 属性4. 构造器5. 方法5.1 allocate 分配 Buffer5.2 wrap 映射数组5.3 slice 获取子 ByteBuffer5.4 duplicate 复刻 ByteBuffer5.5 asReadOnlyBuffer 创建只读的 ByteBuffer5.6 get 方法获取字节5.7 put 方法往 ByteBuffer 里面加入字节5.…

Matplotlib 直方图:数据可视化基础

Matplotlib 直方图&#xff1a;数据可视化基础 直方图是一种常用的数据可视化工具&#xff0c;用于展示数据的分布情况。在Python的数据可视化库Matplotlib中&#xff0c;创建直方图是一项基础而重要的功能。本文将详细介绍如何在Matplotlib中创建和定制直方图&#xff0c;以及…

【面试题】简单聊一下什么是云原生、什么是k8s、容器,容器与虚机相比优势

云原生&#xff08;Cloud Native&#xff09; 定义&#xff1a;云原生是一种构建和运行应用程序的方法&#xff0c;旨在充分利用云计算的优势。它涵盖了一系列技术和理念&#xff0c;包括容器化、微服务架构、自动化部署与管理等。特点&#xff1a;云原生应用程序被设计为可弹性…

C# SQL ASP.NET Web

留学生的课程答疑 按照要求完成程序设计、数据库设计、用户手册等相关技术文档&#xff1b; 要求 1. 计算机相关专业&#xff0c;本科以上学历&#xff0c;至少有1年以上工作经验或实习经历。 2. 熟练掌握WinForm程序开发&#xff0c;或ASP.NET Web编程。 3. 熟悉C#中网络…

大模型(LLM)面试全解:主流架构、训练目标、涌现能力全面解析

系列文章目录 大模型&#xff08;LLMs&#xff09;基础面 01-大模型&#xff08;LLM&#xff09;面试全解&#xff1a;主流架构、训练目标、涌现能力全面解析 大模型&#xff08;LLMs&#xff09;进阶面 文章目录 系列文章目录大模型&#xff08;LLMs&#xff09;基础面一、目…

若依框架--数据字典设计使用和前后端代码分析

RY的数据字典管理: 字典管理是用来维护数据类型的数据&#xff0c;如下拉框、单选按钮、复选框、树选择的数据&#xff0c;方便系统管理员维护。减少对后端的访问&#xff0c;原来的下拉菜单点击一下就需要对后端进行访问&#xff0c;现在通过数据字典减少了对后端的访问。 如…

2025年  生活公报计划

我计划&#xff0c;在2025年将自己生活的一部分进行公开化播报&#xff0c;公报内容基本就是所见所闻所思所想&#xff0c;以及最近要做的事情和已经完成的计划任务。 也可以当做一种生活日志。暂定一年期执行&#xff0c;以后做不做&#xff0c;以后再说。 因为我一直有计划清…

Unity打包+摄像机组件

转换场景 使用程序集&#xff1a;using UnityEngine.SceneManagement; 切换场景相关代码&#xff1a;SceneManager.LoadScene(1);//括号内可放入场景名称&#xff0c;场景索引等 //Application.LoadLevel(""); 老版本Unity加载场景方法 打包相关 Bundle Identi…

蓝桥与力扣刷题(66 加一)

题目&#xff1a; 给定一个由 整数 组成的 非空 数组所表示的非负整数&#xff0c;在该数的基础上加一。 最高位数字存放在数组的首位&#xff0c; 数组中每个元素只存储单个数字。 你可以假设除了整数 0 之外&#xff0c;这个整数不会以零开头。 示例 1&#xff1a; 输入…

stable diffusion 量化学习笔记

文章目录 一、一些tensorRT背景及使用介绍1&#xff09;深度学习介绍2&#xff09;TensorRT优化策略介绍3&#xff09;TensorRT基础使用流程4&#xff09;dynamic shape 模式5&#xff09;TensorRT模型转换 二、实操1&#xff09;编译tensorRT开源代码运行SampleMNIST 一、一些…

省森林防火应急指挥系统

森林防火形势严峻 我国森林防火形势十分严峻&#xff0c;森林火灾具有季节性强、发现难、成灾迅速等特点&#xff0c;且扑救难度大、影响范围广、造成的损失重。因此&#xff0c;构建森林防火应急指挥系统显得尤为重要。 系统建设模式与架构 森林防火应急指挥系统采用大智慧…

drawDB docker部属

docker pull xinsodev/drawdb docker run --name some-drawdb -p 3000:80 -d xinsodev/drawdb浏览器访问&#xff1a;http://192.168.31.135:3000/

79 Openssl3.0 RSA公钥加密数据

1 引言 最近不小心用到了openssl3.0&#xff0c;项目中需要使用rsa非对称加解密算法&#xff0c;所以把openssl3.0使用公钥加密数据的函数调用摸了一遍。 之所以记录此篇文章&#xff0c;是因为网络上大多数是openssl3.0以前的版本的函数接口&#xff0c;而openssl3.0之后已经丢…

【python3】 sqlite格式的db文件获得所有表和数据

【python3】 sqlite格式的db文件获得所有表和数据 1.背景2.代码3.解析1.背景 SQLite 格式的 .db 文件就是一个包含 SQLite 数据库的文件。 SQLite 格式的 .db 文件通常存储的是一个关系型数据库。 SQLite广泛用于应用程序、移动设备、浏览器等场景。它将整个数据库存储在一个文…