Go言語でwasmのサンプルプログラムを作って動かしてみる

JavaScriptを有効にしてください

まえがき

※【2025/2/28 追記】続きを書きました
【Claude 3.7】Go言語でwasmのサンプルプログラムをAIに作らせて動かしてみる

Go言語でボール落下アニメーションのプログラムを書き、Wasmにコンパイルしてブログ上で動かしてみました。
「Start」ボタンを押下すると、Wasmファイルがダウンロードされ、プログラムが実行されます。

tinygoを使用した場合、非圧縮では約200KB、gzipで圧縮すると約80KB程度のファイルサイズになりました。
ブログに載せるには結構大きめのファイルサイズになってしまいますが、許容範囲かなと思います。

そもそも単純なプログラムで使用するパッケージは3種類のみのため、ファイルサイズが小さくなるのは当然ですが、
さまざまな機能を実装すると、圧縮しても1MBを超えてくるでしょう。

ちなみに有名なWebサービスであるFigmaもwasmを使用しています。
Figmaで使用されているwasmのサイズは約9MBでした。

ボール落下アニメーションの作り方

では作り方について解説していきます。
以下で作成するファイルは全てwasm-testフォルダ直下に作成してきます。

1
2
$ mkdir wasm-test
$ cd wasm-test

まずは、ボール落下アニメーションのプログラムをGo言語で作成します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
// main.go
package main

import (
	"math/rand/v2"
	"syscall/js"
	"time"
)

type Ball struct {
	X, Y   float64
	VY     float64
	Radius float64
	Color  string
}

var (
	canvas  js.Value
	ctx     js.Value
	balls   []Ball
	gravity float64 = 0.5 // 重力加速度
	ground  float64 = 300 // 地面の位置
	width   float64 = 500 // キャンバスの幅
	height  float64 = 400 // キャンバスの高さ
)

// 物理演算の更新処理
func updatePhysics() {
	for i := range balls {
		balls[i].VY += gravity // 重力の影響を適用
		balls[i].Y += balls[i].VY

		// 地面に衝突した場合
		if balls[i].Y > ground-balls[i].Radius {
			balls[i].Y = ground - balls[i].Radius
			balls[i].VY = -balls[i].VY * 0.7 // 反発係数
		}
	}
}

// 描画処理
func draw() {
	// キャンバスをクリア
	ctx.Call("clearRect", 0, 0, width, height)

	// 各ボールを描画
	for _, ball := range balls {
		ctx.Call("beginPath")
		ctx.Call("arc", ball.X, ball.Y, ball.Radius, 0, 2*3.1415926535)
		ctx.Set("fillStyle", ball.Color)
		ctx.Call("fill")
	}
}

// requestAnimationFrame のコールバック関数
func render(this js.Value, args []js.Value) interface{} {
	updatePhysics()
	draw()
	js.Global().Call("requestAnimationFrame", js.FuncOf(render))
	return nil
}

func main() {
	r := rand.New(rand.NewPCG(uint64(time.Now().UnixNano()), 3))

	// HTML の canvas 要素を取得
	document := js.Global().Get("document")
	canvas = document.Call("getElementById", "myCanvas")
	if canvas.IsUndefined() {
		js.Global().Call("alert", "Canvas要素が見つかりません!")
		return
	}
	ctx = canvas.Call("getContext", "2d")

	// ボールを複数追加
	for i := 0; i < 5; i++ {
		xx := r.Float64()
		rr := r.Float64()
		balls = append(balls, Ball{
			X:      xx*(width-40) + 20, // ランダムなX座標
			Y:      0,                  // 最初は上部
			VY:     0,                  // 初速度ゼロ
			Radius: 10 + rr*10,         // 10~20px のランダムな半径
			Color:  randomColor(r),
		})
	}

	// アニメーション開始
	js.Global().Call("requestAnimationFrame", js.FuncOf(render))

	// 永久ループ
	select {}
}

// ランダムな色を生成する関数
func randomColor(r *rand.Rand) string {
	colors := []string{"red", "blue", "green", "purple", "orange"}
	return colors[r.IntN(len(colors))]
}

次に、上記Wasmプログラムをフェッチして実行するHTMLを作成します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Go WebAssembly 落下アニメーション</title>
</head>
<body>
  <canvas id="myCanvas" width="500" height="400"></canvas>
  <script src="wasm_exec.js"></script>
  <script>
    // Go の WebAssembly ランタイムの初期化
    const go = new Go();
    // main.wasm を非同期で読み込み、実行する
    WebAssembly.instantiateStreaming(fetch("main.wasm"), go.importObject).then((result) => {
      go.run(result.instance);
    });
  </script>
</body>
</html>

次に、Wasmをロードして実行するための tinygo 用のJSファイル(wasm_exec.js)をカレントディレクトリにダウンロードします。
以下のコマンドを実行してください。

1
$ docker run -v "$(pwd)":/go -e "GOPATH=/go" tinygo/tinygo:0.36.0 /bin/bash -c "cp /usr/local/tinygo/targets/wasm_exec.js /go/"

実行後、wasm_exec.jsがカレントディレクトリに作成されていればOKです。

注意点

このwasm_exec.jstinygo 0.36用のものになっています。
goの公式の方を使用するとWebAssembly.instantiate(): Import #0 module="wasi_snapshot_preview1" error: module is not an object or functionというエラーになってしまうので、tinygo 0.36専用のwasm_exec.jsを使用しましょう。

tinygoでWasmのバイナリを作る

では、tinygoを使ってコンパイルする手順を説明します。

なお、Goのバージョンによってはコンパイルエラーになる場合があるためできれば1.24.0を使用してください。

まず、tinygo 0.36をダウンロードしインストールします。(Ubuntu環境の場合)

1
2
wget https://github.com/tinygo-org/tinygo/releases/download/v0.36.0/tinygo_0.36.0_amd64.deb
sudo dpkg -i tinygo_0.36.0_amd64.deb

次に、プロジェクトを初期化します。

1
$ go mod init wasm-test

そして、tinygoでコンパイルします。

1
tinygo build -o main.wasm -target wasm .

これで、main.wasmが生成されます。
ファイル構成は以下のようになっているはずです。

1
2
3
4
5
6
7
8
9
$ tree wasm-test
wasm-test
├── go.mod
├── index.html
├── main.go
├── main.wasm
└── wasm_exec.js

0 directories, 5 files

では、ローカル環境でWebサーバーを立ち上げて動作確認をしてみましょう。
Pythonで簡易的なWebサーバーを起動します。

1
python3 -m http.server 8080

サーバー起動後、http://localhost:8080 にアクセスします。
下記画像のようにランダムな色のボールが落下してくる画面が表示されたら成功です。
Go言語で書いたプログラムをWasmにしてブラウザ上で実行

あとがき

思ったより簡単に動作したので、かなり作りやすくなっていると感じました。
今後は、Goの勉強も兼ねて、さまざまな機能を少しずつ追加していく予定です。

本記事が何かしらの参考になれば幸いです。
記事内容についてご不明な点がございましたら、下記のXアカウントまでお気軽にDMしてください。
もふもふ-Xアカウント


スポンサーリンク

共有

もふもふ

プロフィール

著者
もふもふ
プログラマ。汎用系→ゲームエンジニア→Webエンジニア→QAエンジニア