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…

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.…

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

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

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

RY的数据字典管理: 字典管理是用来维护数据类型的数据&#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/

C++ STL map和set的使用

序列式容器和关联式容器 想必大家已经接触过一些容器如&#xff1a;list&#xff0c;vector&#xff0c;deque&#xff0c;array&#xff0c;forward_list&#xff0c;string等&#xff0c;这些容器统称为系列容器。因为逻辑结构为线性的&#xff0c;两个位置的存储的值一般是…

26、【OS】【Nuttx】用cmake构建工程

背景 之前wiki 14、【OS】【Nuttx】Nsh中运行第一个程序 都是用 make 构建&#xff0c;准备切换 cmake 进行构建&#xff0c;方便后续扩展开发 Nuttx cmake 适配 nuttx项目路径下输入 make distclean&#xff0c;清除之前工程配置 adminpcadminpc:~/nuttx_pdt/nuttx$ make …

spring boot解决swagger中的v2/api-docs泄露漏洞

在配置文件中添加以下配置 #解决/v2/api-docs泄露漏洞 springfox:documentation:swagger-ui:enabled: falseauto-startup: false 处理前&#xff1a; 处理后&#xff1a;

LayaAir3.2来了:性能大幅提升、一键发布安装包、支持WebGPU、3D导航寻路、升级为真正的全平台引擎

前言 LayaAir3的每一个分支版本都是一次较大的提升&#xff0c;在3.1彻底完善了引擎生态结构之后&#xff0c;本次的3.2会重点完善全平台发布相关的种种能力&#xff0c;例如&#xff0c;除原有的安卓与iOS系统外&#xff0c;还支持Windows系统、Linux系统、鸿蒙Next系统&#…

AI多模态技术介绍:视觉语言模型(VLMs)指南

本文作者&#xff1a;AIGCmagic社区 刘一手 AI多模态全栈学习路线 在本文中&#xff0c;我们将探讨用于开发视觉语言模型&#xff08;Vision Language Models&#xff0c;以下简称VLMs&#xff09;的架构、评估策略和主流数据集&#xff0c;以及该领域的关键挑战和未来趋势。通…

uniapp区域滚动——上划进行分页加载数据(详细教程)

##标题 用来总结和学习&#xff0c;便于自己查找 文章目录 一、为什么scroll-view?          1.1 区域滚动页面滚动&#xff1f;          1.2 代码&#xff1f; 二、分页功能&#xff1f;          2.1 如何实现&#xff…

【大数据】Apache Superset:可视化开源架构

Apache Superset是什么 Apache Superset 是一个开源的现代化数据可视化和数据探索平台&#xff0c;主要用于帮助用户以交互式的方式分析和展示数据。有不少丰富的可视化组件&#xff0c;可以将数据从多种数据源&#xff08;如 SQL 数据库、数据仓库、NoSQL 数据库等&#xff0…