前言
hi,大家好,這裡是白澤。今天給大家分享一個 GitHub 🌟9.8k 的 Go 語言 2D 遊戲引擎。
https://github.com/hajimehoshi/ebiten
引擎的貢獻者依舊在積極維護,是一個兼具學習 & 娛樂的專案!
為此我也用這個引擎寫了一個生存遊戲: avoid-the-enemies【如下圖】:https://github.com/BaiZe1998/avoid-the-enemies
當然詳細的遊戲設計我還是想再透過下一期文章講解,這期的內容主要講解遊戲引擎提供的能力,一些官方有趣的 demo,以及以我的開發為例,講解如何使用這個引擎快速上手開發屬於自己的遊戲。
avoid-the-enemies 玩法
儘可能存活是你唯一要做的事!
遊戲的體驗實況已經錄製並上傳了,歡迎你的關注 📺 B站:白澤talk,QQ群:622383022。
🌟 當然,如果您是一位 Go 學習的新手,您可以在我開源的學習倉庫:https://github.com/BaiZe1998/go-learning 中,找到我往期翻譯的英文書籍,或者Go學習路線,等一系列精彩內容。
有趣的 Demo
在這個遊戲引擎的 examples 目錄下整合很多的遊戲小 demo 以及素材,只要安裝了 go 開發環境就能直接編譯執行了。
而且它支援跨平臺執行的:Win、macOS、Linux、FreeBSD、Android、IOS、Xbox、Siwtch 都不在話下。(支援手柄外設)
Flappy
復刻經典 flappy bird 小遊戲,只是鼠鼠實在是太大了,難度指數:5🌟。
粒子效果
酷炫指數:5🌟。
鋼琴
娛樂指數:5🌟,午休的時候來一曲吧。
引擎核心流程
由於引擎本身幫助開發者完成了多平臺適配,以及最重要的 UI 的渲染,因此開發遊戲只需要關注左側這張圖的三個核心方法即可。
整個引擎就是一個迴圈,這個迴圈每秒大約可以執行60次,那就是達到了所謂的 FPS 60。
這個迴圈中要做的事有兩件事,一部分寫數值,一部分繪製角色。(呼叫上圖左側的兩個 Func)。開發者需要編寫 Game 結構體的 Draw 和 Update 函式,然後交由引擎每秒呼叫大約 60 次。
avoid-the-enemies 拆解
接下來將以我的生存小遊戲為例,講解一下這個遊戲引擎的正確開啟方式。
繪製遊戲視窗
給定一個寬和高,就可以初始化一個遊戲視窗,所有的繪製內容都將以視窗的左上角作為 (0, 0),右為x正軸,下為y正軸控制角色位置。
func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
return screenWidth, screenHeight
}
畫面繪製
下面以我編寫的遊戲為例,講解一下游戲開發流程。
以畫素小人的移動來說,只要我在一秒內不斷更換連續的畫素圖片,遍歷對應的索引,並繪製連貫動作的角色圖片,就能達到動畫的效果了
當然在具體的實現時,我們需要去素材網站上下載對應的圖片,在並且瞭解對應圖片的畫素大小,如上圖每個小人是32px*32px,因此為了達到播放跑動的效果,需要透過迴圈,不斷獲取子圖,然後在一秒內快速替換。
func (g *Game) Draw(screen *ebiten.Image) {
// ...
// 繪製角色
op := &ebiten.DrawImageOptions{}
op.GeoM.Translate(g.player.x, g.player.y)
i := (g.player.count / 5) % frameCount
sx, sy := frameOX+i*frameWidth, frameOY
screen.DrawImage(runnerImage.SubImage(image.Rect(sx, sy, sx+frameWidth, sy+frameHeight)).(*ebiten.Image), op)
// ...
}
數值狀態變更
假設需要實現武器每五秒鐘重新整理,只需要寫一個武器生成器,並在 Update 方法中呼叫,即可得到武器的資料。再配合上述 Draw 方法,將武器渲染出來即可。
// 生成武器
func GenerateWeapon(g *Game) {
if time.Since(g.weaponTimer) > time.Second*5 {
g.weaponTimer = time.Now()
if len(g.weapons) < 2 {
g.uniqueId++
weapon := weaponList[rand.Intn(len(weaponList))]
switch weapon.(type) {
case *MeleeWeapon:
newWeapon := weapon.(*MeleeWeapon).Copy()
// 使用指標型別有複製的bug,當兩個人獲得同一把武器的時候,旋轉會draw兩次,所以看起來轉速快了一倍
g.weapons[g.uniqueId] = newWeapon
g.weaponPosition[g.uniqueId] = f64.Vec2{rand.Float64() * (screenWidth - frameWidth/2), rand.Float64() * (screenHeight - frameHeight/2)}
case *RangedWeapon:
newWeapon := weapon.(*RangedWeapon).Copy()
g.weapons[g.uniqueId] = newWeapon
g.weaponPosition[g.uniqueId] = f64.Vec2{rand.Float64() * (screenWidth - frameWidth/2), rand.Float64() * (screenHeight - frameHeight/2)}
}
}
}
}
func (g *Game) Update() error {
GenerateWeapon(g)
return nil
}
比如需要繪製子彈的移動軌跡,可以透過一個 map 記錄地圖上所有子彈起始位置,目標位置,移動速度,子彈貼圖等內容,透過計時器配合 Update 方法每秒多次更新子彈的資料,最後再借助 Draw 方法,完成子彈的繪製,達到子彈移動的效果。
小節
遊戲開發可能是很多人小時候的一個願望,白澤也一樣,今天實現了,很有趣很開心~
有更多好的 idea 歡迎積極評論。