ebitengine-game-dev

Go の 2D ゲームエンジン Ebitengine (v2) を使ったゲーム開発スキル。プロジェクト構築、スプライト描画、アニメーション、オーディオ(BGM/SE)、キーボード&タッチ入力、シーン管理、WASM ビルド、GitHub Pages デプロイまでを網羅。Use when building 2D games with Go and Ebitengine, adding sprites/audio/touch controls, or deploying Go games to the web via WASM.

webzawa/ebitengine-skill1 installsMITSynced Aug 27

Works with

Claude CodeCursorCodex CLIGitHub CopilotGemini CLI

Agent Skills format with YAML frontmatter. Claude Code reads it as-is.

---
name: "ebitengine-game-dev"
description: "Go の 2D ゲームエンジン Ebitengine (v2) を使ったゲーム開発スキル。プロジェクト構築、スプライト描画、アニメーション、オーディオ(BGM/SE)、キーボード&タッチ入力、シーン管理、WASM ビルド、GitHub Pages デプロイまでを網羅。Use when building 2D games with Go and Ebitengine, adding sprites/audio/touch controls, or deploying Go games to the web via WASM."
license: "MIT"
---

# Ebitengine Game Development Skill

Go の 2D ゲームエンジン [Ebitengine](https://ebitengine.org/) (v2) を使ったゲーム開発の包括的なガイド。
プロジェクトのゼロからの構築、スプライト描画、オーディオ、入力処理、WASM デプロイまでをカバーする。

## When to Use This Skill

- Go で 2D ゲームを開発する
- Ebitengine のプロジェクトをセットアップする
- スプライト画像をゲームに統合する
- BGM / SE(効果音)を実装する
- キーボード&タッチ(スマホ・タブレット)入力を実装する
- シーン管理(タイトル画面・プレイ・ゲームオーバー)を実装する
- Go ゲームを WASM にビルドして GitHub Pages にデプロイする

## Ebitengine の基本アーキテクチャ

### `ebiten.Game` インターフェース(3つの必須メソッド)

```go
type Game struct { /* ゲーム状態 */ }

// Update: ゲームロジック。毎秒60回(60TPS)呼ばれる。
func (g *Game) Update() error { return nil }

// Draw: 画面の描画。毎フレーム呼ばれる。
func (g *Game) Draw(screen *ebiten.Image) {}

// Layout: 論理画面サイズを返す。ウィンドウサイズと独立。
func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
    return screenWidth, screenHeight
}
```

### ゲームの起動

```go
func main() {
    ebiten.SetWindowSize(640, 480)
    ebiten.SetWindowTitle("My Game")
    if err := ebiten.RunGame(&Game{}); err != nil {
        log.Fatal(err)
    }
}
```

### 座標系

- 原点: 左上 (0, 0)
- X軸: 右が正
- Y軸: **下が正**(上に移動 = Y が減る)

## プロジェクトセットアップ

```bash
go mod init <module-name>
go get github.com/hajimehoshi/ebiten/v2
```

### 推奨ディレクトリ構成

```
project/
├── assets/          # 画像・音声ファイル
├── main.go          # ゲーム本体(小規模なら1ファイルで十分)
├── go.mod
├── index.html       # ブラウザ版(WASM デプロイ時)
├── wasm_exec.js     # Go WASM ランタイム(WASM デプロイ時)
└── .github/workflows/deploy.yml  # GitHub Pages 自動デプロイ
```

## アセットの埋め込み(embed)

Go の `embed` パッケージでアセットをバイナリに埋め込む。
WASM ビルドでも外部ファイルアクセス不要になるため **必須**。

```go
import "embed"

//go:embed assets/*.png assets/*.mp3
var assetsFS embed.FS
```

### 画像の読み込み

```go
import "image/png"

func loadImage(name string) *ebiten.Image {
    f, err := assetsFS.Open("assets/" + name + ".png")
    if err != nil {
        log.Fatal(err)
    }
    defer f.Close()
    img, _ := png.Decode(f)
    return ebiten.NewImageFromImage(img)
}
```

## 描画 API

### 背景の塗りつぶし

```go
screen.Fill(color.RGBA{0, 0, 0, 255})
```

### スプライト描画(スケーリング付き)

```go
func drawSprite(screen, sprite *ebiten.Image, x, y, scale float64) {
    op := &ebiten.DrawImageOptions{}
    op.GeoM.Scale(scale, scale)
    op.GeoM.Translate(x, y)
    screen.DrawImage(sprite, op)
}
```

### 色の調整(減光・フィルタ)

```go
op := &ebiten.DrawImageOptions{}
op.ColorScale.Scale(0.3, 0.3, 0.3, 1) // RGB を 30% に減光
screen.DrawImage(img, op)
```

### デバッグテキスト

```go
ebitenutil.DebugPrint(screen, "左上に表示")
ebitenutil.DebugPrintAt(screen, "指定位置", x, y)
```

### 矩形の描画

```go
ebitenutil.DrawRect(screen, x, y, width, height, color.RGBA{...})
```

### 半透明オーバーレイ

```go
overlay := ebiten.NewImage(screenWidth, screenHeight)
overlay.Fill(color.RGBA{0, 0, 0, 160}) // 半透明の黒
screen.DrawImage(overlay, nil)
```

## 入力処理

### キーボード

```go
// キーが押されている間ずっと true
if ebiten.IsKeyPressed(ebiten.KeyArrowUp) { ... }

// キーが押された瞬間だけ true(メニュー操作等に最適)
if inpututil.IsKeyJustPressed(ebiten.KeySpace) { ... }
```

### タッチ入力(スマホ・タブレット)

```go
// 新しいタッチの検出
touchIDs := inpututil.AppendJustPressedTouchIDs(nil)
if len(touchIDs) > 0 {
    id := touchIDs[0]
    x, y := ebiten.TouchPosition(id)
}

// タッチの終了検出
if inpututil.IsTouchJustReleased(id) { ... }
```

### スワイプ検出パターン

```go
const swipeThreshold = 16 // 最小スワイプ距離

type Game struct {
    touchID       ebiten.TouchID
    touchStartX   int
    touchStartY   int
    touchTracking bool
}

func (g *Game) handleSwipe() {
    // 1. 新しいタッチ → touchStart を記録、touchTracking = true
    // 2. タッチ中 → 現在位置と開始位置の差分を計算
    // 3. 差分が swipeThreshold を超えたら方向を確定
    // 4. 確定後、touchStart を現在位置に更新(連続スワイプ対応)
    // 5. タッチ終了 → touchTracking = false
}
```

### タッチ入力のUI判定(ボタンタップ)

```go
// ボタン矩形との当たり判定
touchIDs := inpututil.AppendJustPressedTouchIDs(nil)
for _, id := range touchIDs {
    tx, ty := ebiten.TouchPosition(id)
    if tx >= btnX && tx <= btnX+btnW && ty >= btnY && ty <= btnY+btnH {
        // ボタンがタップされた
    }
}
```

## オーディオ

### 必要なインポート

```go
import (
    "github.com/hajimehoshi/ebiten/v2/audio"
    "github.com/hajimehoshi/ebiten/v2/audio/mp3"
)
```

### BGM(無限ループ再生)

```go
var audioCtx = audio.NewContext(48000)

func initBGM() {
    data, _ := assetsFS.ReadFile("assets/bgm.mp3")
    stream, _ := mp3.DecodeWithoutResampling(bytes.NewReader(data))
    loop := audio.NewInfiniteLoop(stream, stream.Length())
    player, _ := audioCtx.NewPlayer(loop)
    player.SetVolume(0.2)
    player.Play()
}
```

### SE(効果音)— デコード済みデータをメモリ保持

```go
// 起動時に一度だけデコード
func decodeSE(name string) []byte {
    data, _ := assetsFS.ReadFile("assets/" + name + ".mp3")
    stream, _ := mp3.DecodeWithoutResampling(bytes.NewReader(data))
    decoded, _ := io.ReadAll(stream)
    return decoded
}

// 再生時: 毎回新しいプレイヤーを作成(重複再生可能)
func playSE(data []byte) {
    player, _ := audioCtx.NewPlayer(bytes.NewReader(data))
    player.SetVolume(0.33)
    player.Play()
}
```

### 音量制御(ミュート対応)

```go
// BGM の音量をミュート状態に応じて切り替え
func (g *Game) applyVolume() {
    if g.muted {
        bgmPlayer.SetVolume(0)
    } else {
        bgmPlayer.SetVolume(bgmVolume)
    }
}

// SE はミュート中なら再生しない
func playSE(data []byte, muted bool) {
    if muted { return }
    // ...
}
```

## シーン管理パターン

タイトル画面・プレイ中・ゲームオーバーなど、複数の画面を管理する設計パターン。

```go
const (
    sceneTitle    = iota
    scenePlaying
    sceneGameOver
)

type Game struct {
    scene int
    // ... 各シーンの状態
}

func (g *Game) Update() error {
    switch g.scene {
    case sceneTitle:
        g.updateTitle()
    case scenePlaying:
        g.updatePlaying()
    case sceneGameOver:
        g.updateGameOver()
    }
    return nil
}

func (g *Game) Draw(screen *ebiten.Image) {
    switch g.scene {
    case sceneTitle:
        g.drawTitle(screen)
    case scenePlaying:
        g.drawPlaying(screen)
    case sceneGameOver:
        g.drawPlaying(screen) // ゲーム画面を背景に
        g.drawGameOver(screen) // オーバーレイ
    }
}
```

## 移動速度の制御

`Update()` は毎秒60回呼ばれるため、ゲームオブジェクトの移動はフレームカウンタで間引く。

```go
const moveInterval = 15 // 15フレームごと = 4回/秒

func (g *Game) Update() error {
    g.tickCount++
    if g.tickCount < moveInterval {
        return nil // まだ移動タイミングでない
    }
    g.tickCount = 0
    // ここで移動処理
}
```

### 難易度による速度変更

```go
var difficultySpeed = [3]int{20, 12, 7} // Easy, Normal, Hard

// moveInterval の代わりに difficultySpeed[g.difficulty] を使う
if g.tickCount < difficultySpeed[g.difficulty] {
    return nil
}
```

## UI パターン

### ボタン描画(矩形 + 枠線 + テキスト)

```go
// ボタン背景
ebitenutil.DrawRect(screen, x, y, w, h, bgColor)
// 枠線
drawBorder(screen, x, y, w, h, borderColor)
// テキスト(中央揃え)
ebitenutil.DebugPrintAt(screen, label, x+w/2-len(label)*3, y+h/2-4)

func drawBorder(screen *ebiten.Image, x, y, w, h int, c color.RGBA) {
    fx, fy, fw, fh := float64(x), float64(y), float64(w), float64(h)
    t := 2.0
    ebitenutil.DrawRect(screen, fx, fy, fw, t, c)
    ebitenutil.DrawRect(screen, fx, fy+fh-t, fw, t, c)
    ebitenutil.DrawRect(screen, fx, fy, t, fh, c)
    ebitenutil.DrawRect(screen, fx+fw-t, fy, t, fh, c)
}
```

### ポーズボタン(⏸ アイコン)

```go
// 右上に常時表示する小さなポーズボタン
px, py, pw, ph := screenWidth-40, 8, 32, 32
ebitenutil.DrawRect(screen, px, py, pw, ph, color.RGBA{0, 0, 0, 100})
// 2本の縦線で⏸アイコン
barW, barH, gap := 4.0, 22.0, 6.0
cx := float64(px) + float64(pw)/2
cy := float64(py) + 5
ebitenutil.DrawRect(screen, cx-gap/2-barW, cy, barW, barH, color.RGBA{200, 200, 200, 255})
ebitenutil.DrawRect(screen, cx+gap/2, cy, barW, barH, color.RGBA{200, 200, 200, 255})
```

## WASM ビルド & GitHub Pages デプロイ

### ビルド確認

```bash
GOOS=js GOARCH=wasm go build -o game.wasm .
```

### 必要ファイル

1. **`wasm_exec.js`**: Go ランタイムからコピー
   ```bash
   cp "$(go env GOROOT)/lib/wasm/wasm_exec.js" .
   ```

2. **`index.html`**: ゲーム埋め込み用 HTML
   - `<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">`
   - `touch-action: none` でタッチのスクロール・ズームを無効化
   - canvas に `aspect-ratio` を設定して Layout の縦横比と一致させる
   - `WebAssembly.instantiateStreaming(fetch("game.wasm"), go.importObject)`

### GitHub Actions ワークフロー

```yaml
name: Deploy to GitHub Pages
on:
  push:
    branches: [main]
permissions:
  contents: read
  pages: write
  id-token: write
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with:
          go-version-file: go.mod
      - run: GOOS=js GOARCH=wasm go build -o game.wasm .
      - run: |
          mkdir -p _site
          cp game.wasm wasm_exec.js index.html _site/
      - uses: actions/upload-pages-artifact@v3
        with:
          path: _site
  deploy:
    needs: build
    runs-on: ubuntu-latest
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    steps:
      - id: deployment
        uses: actions/deploy-pages@v4
```

### GitHub Pages の有効化(CLI)

```bash
# リポジトリを Public に(無料プランで必要)
gh repo edit <owner/repo> --visibility public --accept-visibility-change-consequences
# Pages を GitHub Actions ソースで有効化
gh api repos/<owner/repo>/pages -X POST -f build_type=workflow
```

## 重要な注意点

### 180度反転防止(スネークゲーム等)

入力を即座に適用せず、`nextDir` バッファに保存して移動タイミングで適用する。

```go
// 入力時: nextDir に保存(逆方向は無視)
if ebiten.IsKeyPressed(ebiten.KeyArrowUp) && g.direction != dirDown {
    g.nextDir = dirUp
}
// 移動時: nextDir を direction に適用
g.direction = g.nextDir
```

### ゲームリスタートのパターン

```go
// ポインタの中身を差し替え。Ebitengine が保持するポインタはそのまま。
*g = *NewGame()
```

### embed と WASM の互換性

`embed` で埋め込んだアセットは WASM ビルドでもそのまま動作する。
`os.Open` はブラウザでは使えないため、常に `embed.FS` を使うこと。

## 参考リンク

- [Ebitengine 公式サイト](https://ebitengine.org/)
- [Ebitengine サンプル集](https://ebitengine.org/en/examples/)
- [API リファレンス (pkg.go.dev)](https://pkg.go.dev/github.com/hajimehoshi/ebiten/v2)
- [wasmgame テンプレート](https://github.com/eihigh/wasmgame)

More General & Other skills

← All General & Other skills

Check your AI visibility

One URL in, a 0–100 score and the exact fixes out.

RUN THE CHECK

Browse all the tools

15 tools across six categories
13 of them never send your data anywhere

Free · No signup · No trial clock

SEE THE DIRECTORY