使用 Golang 編寫 2D 遊戲

Ebitengine 是一款基於 Go 語言的 2D 遊戲開發引擎,提供了簡單易用的 API 和跨平臺的支持,開發者可以通過它輕鬆構建遊戲項目。

  1. 核心特性

  1. 跨平臺支持

Ebitengine 支持多個平臺,包括 Windows、macOS、Linux,以及 WebAssembly(可運行於瀏覽器中)。這使得開發者可以在不同設備上運行同一個遊戲項目,而無需編寫額外的代碼。

  1. 易用的 API

Ebitengine 的 API 設計簡單直觀,Go 開發者可以快速上手。通過定義遊戲邏輯的 Update 和渲染的 Draw 方法,開發者可以輕鬆實現遊戲的核心功能。

  1.  圖像與音頻支持

Ebitengine 內置了圖像渲染和音頻播放的支持,開發者無需依賴額外的庫即可加載圖像資源和播放音效。

  1. 高性能

由於 Go 語言本身的高效性和 Ebitengine 的優化設計,遊戲的運行性能可以滿足大多數 2D 遊戲的需求。

  1. 案例:創建一個基礎的 “打磚塊” 遊戲

下面我們將通過一個簡單的 “打磚塊” 遊戲案例,展示如何使用 Ebitengine 創建一個完整的遊戲。

功能描述

2.1 目錄結構

brickbreaker/
|-- main.go
|-- assets/
    |-- paddle.png
    |-- ball.png
    |-- brick.png

圖片可以使用 python 進行生成簡單的,大家也可以去一些資源網站通過搜索關鍵字(比如磚塊,球,擋板)來獲取

from PIL import Image, ImageDraw

# 生成 paddle.png
paddle = Image.new("RGBA", (100, 20)"white")
paddle.save("paddle.png")

# 生成 ball.png
ball = Image.new("RGBA", (16, 16), (0, 0, 0, 0))  # 透明背景
draw = ImageDraw.Draw(ball)
draw.ellipse((0, 0, 15, 15)fill="white")
ball.save("ball.png")

# 生成 brick.png
brick = Image.new("RGBA", (50, 20)"red")
brick.save("brick.png")

2.2 代碼實現

package main

import (
    "github.com/hajimehoshi/ebiten/v2"
    "github.com/hajimehoshi/ebiten/v2/ebitenutil"
    "image/color"
    "log"
    "strconv"
)

const (
    ScreenWidth  = 800
    ScreenHeight = 600
    PaddleWidth  = 100
    PaddleHeight = 20
    BallSize     = 16
    BrickWidth   = 75
    BrickHeight  = 20
    BrickRows    = 5
    BrickColumns = 10
)

type Brick struct {
    x      float64
    y      float64
    active bool
}

type Game struct {
    paddleX  float64
    ballX    float64
    ballY    float64
    ballVX   float64
    ballVY   float64
    bricks   []Brick
    score    int
    gameOver bool
}

func (g *Game) initializeBricks() {
    g.bricks = nil
    padding := (ScreenWidth - (BrickColumns * BrickWidth)) / (BrickColumns + 1)
    for row := 0; row < BrickRows; row++ {
        for col := 0; col < BrickColumns; col++ {
            brick := Brick{
                x:      float64(padding + col*(BrickWidth+padding)),
                y:      float64(50 + row*(BrickHeight+10)), // 距離頂部 50px
                active: true,
            }
            g.bricks = append(g.bricks, brick)
        }
    }
}

func (g *Game) Update() error {
    if g.gameOver {
        return nil
    }

    // 玩家控制擋板
    if ebiten.IsKeyPressed(ebiten.KeyLeft) {
        g.paddleX -= 5
        if g.paddleX < 0 {
            g.paddleX = 0
        }
    }
    if ebiten.IsKeyPressed(ebiten.KeyRight) {
        g.paddleX += 5
        if g.paddleX > ScreenWidth-PaddleWidth {
            g.paddleX = ScreenWidth - PaddleWidth
        }
    }

    // 更新小球位置
    g.ballX += g.ballVX
    g.ballY += g.ballVY

    // 小球反彈邏輯
    if g.ballX < 0 || g.ballX > ScreenWidth-BallSize {
        g.ballVX *= -1
    }
    if g.ballY < 0 {
        g.ballVY *= -1
    }

    // 小球與擋板碰撞檢測
    if g.ballY+BallSize >= ScreenHeight-PaddleHeight &&
        g.ballX+BallSize >= g.paddleX && g.ballX <= g.paddleX+PaddleWidth {
        g.ballVY *= -1
    }

    // 小球與磚塊碰撞檢測
    for i := range g.bricks {
        brick := &g.bricks[i]
        if brick.active &&
            g.ballX+BallSize > brick.x && g.ballX < brick.x+BrickWidth &&
            g.ballY+BallSize > brick.y && g.ballY < brick.y+BrickHeight {
            brick.active = false
            g.ballVY *= -1
            g.score += 10
            break
        }
    }

    // 遊戲結束檢測:小球掉出屏幕或所有磚塊被擊毀
    if g.ballY > ScreenHeight || g.allBricksDestroyed() {
        g.gameOver = true
        log.Printf("Game Over! Final Score: %d\n", g.score)
    }

    return nil
}

func (g *Game) allBricksDestroyed() bool {
    for _, brick := range g.bricks {
        if brick.active {
            return false
        }
    }
    return true
}

func (g *Game) Draw(screen *ebiten.Image) {
    // 繪製背景
    screen.Fill(color.RGBA{0, 0, 0, 255})

    // 繪製擋板
    paddleRect := ebiten.NewImage(PaddleWidth, PaddleHeight)
    paddleRect.Fill(color.White)
    geoM := ebiten.GeoM{}
    geoM.Translate(g.paddleX, ScreenHeight-PaddleHeight)
    screen.DrawImage(paddleRect, &ebiten.DrawImageOptions{
        GeoM: geoM,
    })

    // 繪製小球
    ballRect := ebiten.NewImage(BallSize, BallSize)
    ballRect.Fill(color.White)
    screen.DrawImage(ballRect, &ebiten.DrawImageOptions{
        GeoM: func() ebiten.GeoM {
            geom := ebiten.GeoM{}
            geom.Translate(g.ballX, g.ballY)
            return geom
        }(),
    })

    // 繪製磚塊
    for _, brick := range g.bricks {
        if brick.active {
            brickRect := ebiten.NewImage(BrickWidth, BrickHeight)
            brickRect.Fill(color.RGBA{255, 0, 0, 255})
            geom := ebiten.GeoM{}
            geom.Translate(brick.x, brick.y)
            screen.DrawImage(brickRect, &ebiten.DrawImageOptions{
                GeoM: geom,
            })
        }
    }

    // 繪製分數
    ebitenutil.DebugPrint(screen, "Score: "+strconv.Itoa(g.score))

    // 遊戲結束提示
    if g.gameOver {
        ebitenutil.DebugPrintAt(screen, "Game Over! Press ESC to exit.", 300, ScreenHeight/2)
    }
}

func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
    return ScreenWidth, ScreenHeight
}

func main() {
    game := &Game{
        paddleX: ScreenWidth / 2,
        ballX:   ScreenWidth / 2,
        ballY:   ScreenHeight / 2,
        ballVX:  4,
        ballVY:  -4,
    }
    game.initializeBricks()

    if err := ebiten.RunGame(game); err != nil {
        log.Fatal(err)
    }
}

你將看到一個簡單的遊戲界面,使用鍵盤方向鍵控制擋板,並嘗試反彈小球擊打磚塊。

2.3 環境說明

  1. 確保你已經安裝了 Go 語言環境。

  2. 安裝 Ebitengine:

    go get github.com/hajimehoshi/ebiten/v2
  3. 運行代碼:

    go run main.go
  4. 結語


Ebitengine 爲 Go 開發者提供了強大的 2D 遊戲開發能力,案例中的 “打磚塊” 遊戲僅展示了其基本功能。通過靈活使用其 API,你可以開發更復雜的遊戲,如 RPG、平臺跳躍、射擊等。未來,你可以嘗試結合 Ebitengine 的音效、動畫等高級特性,爲你的遊戲增添更多樂趣與深度。

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