GitHub 9-8k 的 Go 語言 2D 遊戲引擎 ebiten

前言

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 方法,完成子彈的繪製,達到子彈移動的效果。

小節

遊戲開發可能是很多人小時候的一個願望,白澤也一樣,今天實現了,很有趣很開心~

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/z4Q7FGhPsLGh3MIJs97puA