Objective-C
Go
GoDay 21

Go/Mac/OpenGL でゲームを作ってみた (が間に合わなかった)

More than 5 years have passed since last update.


キャプチャ

blocks1.png

blocks2.png

よくある落ち物ゲームっぽいものです。 12/21 時点で、ブロックの自然落下や消去が未実装なので、ひたすら積み上げるだけです。

blocks3.png


プレイ方法

Mac OS X でしか動きません。

$ go get https://github.com/hajimehoshi/go-ebiten

$ cd $GOPATH/github.com/hajimehoshi/go-ebiten/example
$ go run main.go


  • ←: 左移動

  • →: 右移動

  • ↓: 下移動

  • Space: 回転


いろいろ


Go でゲームプログラミングを書くのはどうなのか

Go だからゲームプログラミングが特別書きやすいとか書きにくいとかは無いような気がします。コンパイルが早いので、修正と確認のサイクルが早いのはいいですね。


cgo

OpenGL の C の API を呼ぶためには cgo を使います。また、 C だけではなく C++ や Objective-C も使えます。特に Objective-C が使えるおかげで、 Cocoa ライブラリを呼ぶことができます。今回は cgo を使って Cocoa の NSWindow や NSOpenGLContext を扱うプログラムを書きました。

Go から OpenGL を使うだけならば、 cgo ではなく go-glgo-gl の glfw を使うのが楽で良いと思います。今回はあえて cgo を使いましたが、ウィンドウなどを細かくカスタマイズしたかったというのが理由です。

Objective-C から Go の関数を呼び出すには、 export を使います ()。


runtime.LockOSThread

Mac はマルチスレッドな OpenGL に対応しています。OpenGL 関数はそのときのスレッドローカルなコンテキスト (NSOpenGLContext) に対して実行されます。 goroutine はある OS のスレッド上で実行されますが、実行中に OS のスレッドが変わってしまうことがあります。これを防ぐために、 runtime.LockOSThread の呼び出しを goroutine で呼ぶ関数の頭に加えます ()。

OS のメインスレッドを実行する goroutine を固定する場合は、 init 関数で runtime.LockOSThread を呼ぶのが定石のようです


channel の close と受信

channel のレシーバが値を受け取らなくても良い場合があります。たとえば、channel を使って単純なブロッキングを行う場合です。こういう場合、 close するだけでレシーバーの処理を終わらせることができます。 close された channel の受信は即座に返ります。

package main

import "fmt"

func main() {
ch := make(chan struct{})
go func() {
fmt.Println("Foo")
close(ch)
}()
<-ch
fmt.Println("Bar")
}

channel の型は struct{} (空構造体) にしていますが、どうせ何も送らないので何でも良いです。空構造体は 0 バイトなのでこういう目的にうってつけだと思ったのですが、 0 バイトであるという情報がどこにあったのか失念してしまいました。


動的ダックタイピング

今回のゲームプログラミングで実際に使っているわけではないですが、面白かったので書きます。

package main

import "fmt"

type Foo struct {}

func (f *Foo) Hello(str string) {
fmt.Println("Hello,", str)
}

func main() {
var foo interface{}
foo = &Foo{}
foo.(interface{ Hello(string) }).Hello("World")
}


免責事項

この文章は私個人の見解を述べたものであり、私の所属している団体等とは一切の関係がありません。