我用 GitHub 9.8k 的 Go 語言 2D 遊戲引擎寫了個遊戲

白泽talk發表於2024-05-06

前言

hi,大家好,這裡是白澤。今天給大家分享一個 GitHub 🌟9.8k 的 Go 語言 2D 遊戲引擎。

https://github.com/hajimehoshi/ebiten

引擎的貢獻者依舊在積極維護,是一個兼具學習 & 娛樂的專案!

為此我也用這個引擎寫了一個生存遊戲: avoid-the-enemies【如下圖】:https://github.com/BaiZe1998/avoid-the-enemies

當然詳細的遊戲設計我還是想再透過下一期文章講解,這期的內容主要講解遊戲引擎提供的能力,一些官方有趣的 demo,以及以我的開發為例,講解如何使用這個引擎快速上手開發屬於自己的遊戲。

image-20240428204456063

avoid-the-enemies 玩法

儘可能存活是你唯一要做的事!

image-20240428220026572

遊戲的體驗實況已經錄製並上傳了,歡迎你的關注 📺 B站:白澤talk,QQ群:622383022。

image-20240429092703509

🌟 當然,如果您是一位 Go 學習的新手,您可以在我開源的學習倉庫:https://github.com/BaiZe1998/go-learning 中,找到我往期翻譯的英文書籍,或者Go學習路線,等一系列精彩內容。

有趣的 Demo

在這個遊戲引擎的 examples 目錄下整合很多的遊戲小 demo 以及素材,只要安裝了 go 開發環境就能直接編譯執行了。

而且它支援跨平臺執行的:Win、macOS、Linux、FreeBSD、Android、IOS、Xbox、Siwtch 都不在話下。(支援手柄外設)

image-20240428203127172

Flappy

復刻經典 flappy bird 小遊戲,只是鼠鼠實在是太大了,難度指數:5🌟。

image-20240428203725614


粒子效果

酷炫指數:5🌟。

image-20240428205602920

鋼琴

娛樂指數:5🌟,午休的時候來一曲吧。

image-20240428205952526

引擎核心流程

image-20240428202143238

由於引擎本身幫助開發者完成了多平臺適配,以及最重要的 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
}

畫面繪製

下面以我編寫的遊戲為例,講解一下游戲開發流程。

image-20240428213425140

以畫素小人的移動來說,只要我在一秒內不斷更換連續的畫素圖片,遍歷對應的索引,並繪製連貫動作的角色圖片,就能達到動畫的效果了

image-20240428213509200

當然在具體的實現時,我們需要去素材網站上下載對應的圖片,在並且瞭解對應圖片的畫素大小,如上圖每個小人是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 方法,完成子彈的繪製,達到子彈移動的效果。

image-20240428222012933

小節

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

有更多好的 idea 歡迎積極評論。

相關文章