我用Ebiten在40分钟内制作了一个2D游戏作品。
这张照片由Leon Pauleikhoff在Unsplash上分享。标题为:“为这个项目,我为什么选择了Golang?”
让我带你走进我的故事。
虽然我不是专业的开发者,也没有在电视上展示过我的编程技能,但我曾经领导过几个开发团队,懂得如何逐步构建产品。我知道何时需要征求反馈,以确保我们的产品能够满足用户需求。我助力推出过令用户兴奋的产品。
对于这次业余项目,我从一个简单的想法开始。我想避免那些不必要的复杂性,以便能更快地学习编程概念。我曾经为了好玩建过一些安卓应用,但我想开发一个可以直接编译到Windows上的程序,无需使用模拟器,无需担心跨平台问题。
我选择了Golang。它设计得简单、快速且直观。我想要一种让我打字时感觉自然的语言。同样重要的是,我希望我的代码能够便于调试。这样,我就能清晰地解释程序中的结构体、函数等内容。
那么,我具体做了什么呢?
我制作了一款名为《蝎子与法拉利》的游戏。这个名字灵感来源于Frogger及其游戏机制。虽然这个想法有些荒谬,但我迅速学到了很多关于游戏开发的知识。在这个项目中,我使用了一个名为Ebiten的库,这是一个专为Go语言开发的开源游戏引擎。通过Ebiten,我利用它简单易用的API和各种函数,迅速且轻松地开发出一个可以在多个平台上运行的游戏。
现在我正在部署到Windows上,但你也可以在macOS和Linux上运行它。当我们编译程序时,会在和代码相同的目录下生成一个可执行文件。这意味着我们甚至可以在Windows上构建程序,然后在Linux上运行相同的程序,这真是太酷了。
那么,我们从哪里开始呢?
你需要在你的操作系统上安装Go语言(Golang)。你可以从Go语言下载页面(go.dev/dl/)开始安装。
接下来,运行Ebiten的示例作为一个概念验证(PoC)。为了快速开始,我建议你尝试Ebiten的hello world示例:ebitengine.org/en/tour/hello_world.html。
然后,你可以开始新建你的Go项目。
照片由Kelly Sikkema拍摄,同样来自Unsplash。我推荐使用VS Code作为这个项目的IDE。VS Code自带的Go插件提供了许多有用的功能,如IntelliSense(智能感知)、代码导航、符号查找、测试、调试等,这些功能在Go开发中非常有帮助。
在Git Bash或命令提示符中,你可以按照以下步骤操作:
1. 创建一个新的游戏目录并切换到该目录。
2. 使用`go mod init`初始化一个新的Go模块。
3. 在终端中输入'code .'来打开VS Code(或者在任何IDE中打开该目录)。
接下来,我们需要安装ebiten模块。在go.mod文件所在的目录中,运行以下命令:`go get github.com/hajimehoshi/ebiten/v2`。这将获取最新版本的ebiten库。
新建一个Main.go文件。这是存储你程序核心逻辑的地方。让我们从添加main包和导入相关库开始。
在你的 Main.go 文件中,我们需要添加以下代码片段:
```go
package main
import (
"fmt" // 用于格式化的输入输出功能,类似于 C 语言中的 printf 和 scanf 函数
"image" // 用于加载和解码图像文件
_ "image/png" // 用于处理 PNG 图像格式的特殊库,下划线表示仅导入其特定的包功能而不导入其错误处理函数等额外内容
"log" // 用于记录程序运行时的日志信息
"math/rand" // 用于生成随机数,用于游戏中的随机事件和车辆生成等场景
"os" // 提供操作系统级别的功能,如读取文件等
"time" // 提供时间相关的功能,用于控制游戏帧率等时间相关操作
"github.com/hajimehoshi/ebiten/v2" // 游戏开发框架,用于渲染游戏画面和事件处理等核心功能
"github.com/hajimehoshi/ebiten/v2/ebitenutil" // 提供一些实用工具函数,用于简化游戏开发过程
"github.com/hajimehoshi/ebiten/v2/vector" // 提供向量运算功能,用于处理游戏对象的移动和碰撞检测等物理计算
"image/color" // 用于处理图像的颜色信息,用于渲染游戏画面中的颜色信息。
)
对于编程新手来说,这些代码可能看起来有些复杂。但当你熟悉编程后,你会发现这些库在其他编程语言中也有类似的功能。在程序的开头引入这些库是为了重用已经存在的代码。例如,当程序需要解码一张图片时,由于我们已经引入了相应的库,所以程序可以直接使用这些库的功能来完成解码操作。
接下来是定义整型常量部分。在这里,我定义了一系列与游戏逻辑相关的常量值。例如屏幕尺寸、网格大小、玩家的移动速度等。这些常量的值对于游戏的整体逻辑和游戏体验至关重要。想象一下我正在开发一个类似Frogger的游戏。玩家需要控制一个角色(一只蝎子)来避开穿越水面公路的汽车。目标是避免被像素化的法拉利车撞到,并在规定时间内到达屏幕的另一端。创建这些常量是第一步,虽然具体的数值可能需要一些调整和优化。
在虚拟的游戏世界中,我们定义了两种核心结构体:GameObject 和 Game。让我们深入了解它们的功能与特点。
我们有一个 GameObject 结构体,它代表游戏中的对象。这个结构体包含了多个属性,如 x 和 y 坐标,表示对象在游戏界面上的位置;speed 属性表示对象的移动速度;image 属性用于存储对象的图像信息;而 width 和 height 则表示对象的尺寸大小;isRight 属性用来判断对象是否向右移动。这些属性共同构成了游戏中的一个基本单位。
接下来是 Game 结构体,它代表了整个游戏的核心部分。在这个结构体中,我们有玩家对象 player,它是玩家在游戏中的代表;background 属性用于存储背景图像;objects 是一个存储各种物体图像的集合;cars 属性则是一个包含多个 GameObject 的集合,代表了游戏中的车辆;currentTime 表示当前的游戏时间,而 lastUpdateTime 则记录了上一次更新的时间;gameState 表示游戏的状态。
现在,我们迎来了新游戏的功能介绍。我们有一个新游戏初始化函数。这个函数使用默认设置来初始化新游戏,加载所需的图片资源,并设置游戏的初始状态。这个过程至关重要,它为游戏的顺利进行奠定了基础。
接下来是“开始游戏”的方法。这个方法的主要任务是设定玩家的初始位置,并随机分配车辆在车道中的位置和速度。这个过程需要精细的计算和规划,以确保玩家角色能够在车辆之间安全移动。我们花费了大量时间来确定蝎子角色在给定车流密度和速度下,在车辆之间所需的具体空间是多少。这是一个技术难题,但也是我们游戏开发的乐趣所在。通过这个方法,我们为玩家提供了一个充满挑战和乐趣的游戏环境。
我们的游戏设计充满了细节和趣味性。通过 GameObject 和 Game 这两个核心结构体的设计,以及新游戏初始化函数和开始游戏的方法,我们为玩家提供了一个丰富而有趣的游戏体验。在一个新的游戏时代,我们的游戏世界正在被重新定义。当您启动这款游戏时,它首先会启动一个名为“初始化游戏函数”的过程。这个过程如同为一场精彩的竞赛搭建舞台。它精心安排玩家的起点和车辆的分布,为即将上演的游戏场景铺设基石。
玩家的位置被精准地设定在舞台的中心。玩家的初始位置被设定为 `g.player.x = float64(gridWidth / 2 gridSize)` 和 `g.player.y = float64((gridHeight - 1) gridSize)`,让玩家能够立刻感受到身临其境的游戏体验。而玩家的起点坐标就像是赛车场上的起跑线,预示着即将上演的速度与激情。
接下来,游戏会清除现有的车辆,为新的比赛做好准备。然后,在每条赛道上,游戏会精心地布置更多的车辆。这个过程就像是在棋盘上下棋,每一步都要经过深思熟虑。这些车辆的位置会被精确地计算出来,它们会在赛道上呈现不同的行驶轨迹和速度。这种动态的布置方式不仅增加了游戏的趣味性,也让玩家感受到了紧张刺激的游戏氛围。
游戏更新之旅
在一个充满活力和刺激的游戏世界中,我们的Game类正在执行一个关键的Update()方法。这不是一场普通的游戏,而是一场速度与激情的较量。让我们深入了解这个方法的精彩内容。
当游戏状态不是“playing”时,如果玩家按下空格键,游戏会初始化,设置当前时间为60秒,并将游戏状态切换为“playing”。此刻,玩家将准备好迎接挑战。
时间在不断流逝,我们获取当前时间并计算自上次更新时间以来的时间差。根据玩家按下的方向键,他们的角色会在二维网格上以特定的速度和精度移动。这些移动指令非常关键,因为它们决定了玩家的行动和在游戏世界中的位置。
我们还要确保玩家不会离开游戏屏幕的范围。为此,我们将玩家的x和y坐标限制在屏幕的特定范围内。这是确保游戏体验流畅性和防止玩家角色离开屏幕的必要步骤。
我们还要更新游戏中的车辆。每辆车的移动方式取决于它们是否在屏幕的右侧。它们在网格上以特定的速度和时间差移动。如果车辆移动到屏幕的边缘,它们会在对面边缘重新出现,形成一个循环的游戏环境。
接下来,我们有一个重要的碰撞检测环节。我们创建一个玩家矩形,并与每辆车的矩形进行碰撞检测。如果玩家与任何车辆发生碰撞,游戏状态将更改为“lose”。这个检测过程至关重要,因为它决定了游戏的胜负和玩家的命运。
绘制流程解析
在`Game`结构体的`Draw`方法中,我们逐步绘制背景、汽车、玩家以及游戏状态。这不仅展示了游戏的实时动态元素,还反映了游戏的状态信息。
绘制背景
我们绘制背景图像,为游戏场景设定基调。
```go
func (g Game) Draw(screen ebiten.Image) {
// 绘制背景图像
options := &ebiten.DrawImageOptions{}
screen.DrawImage(g.background, options)
// ...后续绘制操作
}
```
绘制汽车和玩家
接着,我们遍历游戏中的汽车,并根据其位置进行绘制。随后,根据玩家的位置和图像进行绘制。如果玩家图像为空,则绘制一个红色的占位矩形。
```go
// 绘制汽车
for _, car := range g.cars {
options := &ebiten.DrawImageOptions{}
options.GeoM.Translate(car.x, car.y)
screen.DrawImage(car.image, options)
}
// 绘制玩家
if g.player.image != nil {
options := &ebiten.DrawImageOptions{}
options.GeoM.Translate(g.player.x, g.player.y)
screen.DrawImage(g.player.image, options)
} else {
log.Println("玩家图像为空,无法绘制玩家")
// 使用红色矩形表示玩家的占位
vector.DrawFilledRect(screen, g.player.x, g.player.y, g.player.width, g.player.height, color.RGBA{255, 0, 0, 255}, false)
}
```
绘制游戏状态和实时信息
我们在屏幕上显示当前时间以及游戏状态(如“你赢了!”或“游戏结束!”),并提供重新开始游戏的选项。
```go
// 调试打印当前时间
ebitenutil.DebugPrint(screen, fmt.Sprintf("当前时间: %d", g.currentTime))
// 根据游戏状态显示不同信息
if g.gameState == "win" {
ebitenutil.DebugPrint(screen, "你赢了!按空格键重新开始游戏")
} else if g.gameState == "lose" {
ebitenutil.DebugPrint(screen, "游戏结束!按空格键重新开始游戏")
}
```
布局设定
`Layout`函数用于确定游戏的屏幕大小。它接收外部宽度和高度作为参数,并返回屏幕的宽度和高度。这对于确保游戏在各种设备上都能良好地运行至关重要。
```go
func (g Game) Layout(externalWidth, externalHeight int) (int, int) {
return screenWidth, screenHeight // 这里应返回具体的宽度和高度值,而非变量名。这些值通常在初始化游戏时设定。
}
```
游戏启动与限幅功能
设置好窗口大小和标题后,使用Ebiten的`RunGame`函数启动游戏。我们还提供了一个`clamp`函数,确保值处于指定范围内。这对于游戏中的物理计算和数值调整非常有用。
启动游戏
```go
func main() {
ebiten.SetWindowSize(screenWidth, screenHeight) // 设置窗口大小,这里的值需要根据实际情况进行设定。
ebiten.SetWindowTitle("蝎子和法拉利") // 设置窗口标题,体现游戏的主题。下面进行游戏的运行和初始化操作。如果运行出错,则记录错误信息并退出程序。确保路径正确并加载必要的资源文件等。最后调用RunGame函数启动游戏循环。具体的错误处理逻辑可以根据实际情况进行调整和优化。这样就完成了游戏的启动流程。剩下的部分是对限幅功能的实现以及对代码的进一步组织和管理。这包括将完整的代码和文档存放在GitHub仓库中供他人参考和使用等后续工作。同时提醒用户注意图像的存放路径和格式等细节问题以确保游戏的正常运行和体验优化等后续操作和改进方向的建议等细节问题也是非常重要的部分对于初学者来说了解和学习图像文件的处理方法和格式选择也是很重要的环节此外在实际开发过程中还需要考虑游戏的逻辑设计算法实现性能优化用户体验优化等方面的问题以确保最终的游戏产品能够满足用户的需求并具有良好的用户体验和游戏性能优化等方面的问题也需要关注和学习掌握相关的技术和工具来提高开发效率和游戏质量最后通过不断的学习和实践来不断提升自己的游戏开发技能和能力为未来的职业发展打下坚实的基础总的来说游戏开发是一个富有挑战性和创造性的过程需要不断的学习和实践才能不断提高自己的技能和经验成为优秀的游戏开发者如果你有兴趣的话不妨尝试自己开发一款游戏来体验其中的乐趣和挑战吧!通过不断的努力和实践你一定能够成为一名出色的游戏开发者!加油!"}` 游戏启动与限幅功能解析及最终代码展示:在Game结构体的main函数中启动游戏循环后,我们将通过调用Ebiten的RunGame函数来启动游戏并渲染每一帧画面首先我们需要设置窗口的大小和标题以匹配游戏的实际需求和设计风格然后通过调用RunGame函数开始运行游戏循环在这个过程中我们需要确保所有的资源文件都已正确加载并且路径正确以避免出现错误在游戏运行过程中我们可以使用限幅功能来确保某些数值在指定的范围内这对于控制游戏中的物体行为和物理计算非常重要此外我们还提供了一些辅助函数如clamp函数用于限制值在最小值和最大值之间以避免出现不合理的计算结果最后我们将完整的代码和文档存放在GitHub仓库中供他人参考和使用同时提醒用户注意图像的存放路径和格式等细节问题以确保游戏的正常运行同时在实际开发过程中还需要关注游戏的逻辑设计算法实现性能优化用户体验优化等方面的问题以不断提升自己的游戏开发技能和能力最终成为优秀的游戏开发者以下是对代码的最终展示和整理(注意这里只是一个大致的框架需要根据实际情况进行调整和优化):首先我们需要包含必要的头文件和库文件并定义好相关的结构体和变量然后我们可以编写主函数来启动游戏循环并设置窗口大小和标题接着我们可以编写Draw函数来绘制每一帧画面包括背景汽车玩家和游戏状态等信息我们还可以编写Layout函数来设置游戏的布局和尺寸同时我们可以添加一些辅助函数如clamp函数用于数值的限幅功能最后我们可以将完整的代码存放在GitHub仓库中供他人参考和使用需要注意的是在实际开发中还需要关注图像处理格式选择逻辑设计算法实现性能优化用户体验优化等方面的问题以确保最终的游戏产品能够满足用户的需求并具有优良的性能和体验总的来说游戏开发是一个富有挑战性和创造性的过程需要不断的学习和实践才能不断提高自己的技能和经验成为优秀的游戏开发者如果你有兴趣的话不妨尝试自己开发一款游戏来体验其中的乐趣和挑战吧!选择您的图片并运用您个人的Paint.NET风格进行创作吧
这一步是可选的。您手中的照片,由Illán Riestra Nava拍摄,来自Unsplash。想要进行图像编辑?我推荐您从他们的github releases页面下载Paint.NET。这款免费软件能让你随心所欲地修改位图。在Paint.NET的世界里,每一位创作者都可以将图像缩小到像素级别,以像素为单位进行细致的调整,再将其呈现为二维的艺术作品。
对于我的背景图片,我选择了充满创意的空白青蛙图案。在Paint.NET这款图像编辑软件中,背景的尺寸至少要与我们定义的窗口大小相匹配。假设屏幕宽度为640像素,高度为480像素,在我的项目中已经按照这些规格进行设置。
真正的艺术在于你的选择。背景的选择是否自然,是否和谐,完全取决于你的感觉和创意。除了背景,我们还要考虑游戏内的元素,如玩家和车辆的尺寸。
根据我们程序中定义的窗口大小,玩家和物体的尺寸应该按比例调整。这样做不仅有助于游戏的平衡性,还能让游戏在视觉上更加协调。以网格大小为基准,玩家的尺寸应该与之相匹配。例如,考虑到网格大小为32像素,玩家的宽度和高度可以设定为32x32像素。要确保玩家的初始位置和移动都遵循网格规则,避免与其他游戏对象发生意外的重叠。
至于汽车,其宽度可以超过网格大小,以营造更大的规模感和增加游戏难度。设定为64像素(即两个网格单元)的宽度是比较合适的。而高度方面,为了使其适应车道,可以设定为与网格大小相同的32像素。
推荐使用Paint.NET软件进行图像编辑。你可以使用界面上的缩放按钮,将你的角色和汽车缩放到推荐尺寸。至于如何去除对象周围的多余空白区域,你可以尝试使用套索工具或矩形选框工具,然后利用“通过选区裁剪”功能进行清理。但这只是一个建议,有时候最好的方法就是尝试不同的图片和尺寸组合,看看实际效果如何——可能会带来意想不到的效果!如果您在阅读过程中有任何疑问或需要交流,欢迎在LinkedIn上与我联系。我的LinkedIn链接如下:[
文章从网络整理,文章内容不代表本站观点,转账请注明【蓑衣网】