Contents
たまにはゲームでも作ろうよ
あーゲームが作りたいですね。
僕は普段webサービスのコードしか書かないのですが、
たまにはゲームとかのコーディングもしたいですよね。
ゲームのコーディングってプログラミングの勉強に最適だと思うんですよね。
Goでゲームを作りたーい!
いい感じのGoのゲーム用パッケージをみつけてきました。
この記事の対象者
Goの環境を自力で作れて、チュートリアルぐらいはやったことがある人です。
それ以外の人は下のリンクからGoの世界に入門してください。
golang-jp.org
engo
Golangの2Dゲームエンジンです。
GitHubで公開されています。
今回はengoのチュートリアルをやってみたいと思います。
windowを表示する
今回は
$GOPATH/src/GoGame
というディレクトリーを作って作業をしていきたいと思います。
engoではゲームはいくつかのシーンで構成されています。
シーンを切り替えることで、ゲームを表現します。例えばタイトル画面、メニュー画面、ゲーム画面
と言ったものがシーンになるのではないかとおもいます。

次のコードではシーンはありませんが、実行するとwindowが現れます。
package main import ( "engo.io/engo" "engo.io/ecs" ) type myScene struct {} // Type uniquely defines your game type func (*myScene) Type() string { return "myGame" } // Preload assetsのロード func (*myScene) Preload() {} // メインループの前に実行される関数 // シーンに対してエンティティやシステムを追加する処理などを書く func (*myScene) Setup(*ecs.World) {} func main() { opts := engo.RunOptions{ Title: "Golangのゲーム", Width: 400, Height: 400, } engo.Run(opts, &myScene{}) }

もうこれだけでワクワクしてきますね。
次はwindowに画像表示してみましょう。
画像を描画しよう
画像を用意する
$GOPATH/goGame以下にassetsというディレクトリーを作成します。
ここには画像や音ファイルなどをおいておく場所です。
せっかくGolangでやっているのでGopherくんの画像を表示してみたいと思います。
以下の画像をgopher.pngという名前で
$GOPATH/src/goGame/assets/textures
以下に保存します。
画像をゲーム内に読み込む
次のようにしてPreload関数内でGopherくんの画像をメモリに読み込みます。
func (*myScene) Preload() { engo.Files.Load("textures/gopher.png") }
画像の描画
画像の描画には以下の3つの要素が必要です。
- System
- Entity
- 2つのComponent(RenderComponent SpaceComponent)
ゲームではたくさんのEntityが作られます。
操作キャラクターや敵キャラクター、アイテムなどあらゆるものがEntityという単位で実装されます。
他のゲームエンジンだとオプジェクトやスプライトと言っているものだと思います。
Entityはいくつかの値をComponentという形で持つことが出来ます。
例えばSpaceComponentはEntityのゲーム内の位置情報を表す値を持っています。
RenderComponentは画像の情報を持っています。
Component自体が何かをするわけではなくあくまでデータコンテナです。

実際に値を操作したりコンポーネントをつけたり消したりするのはSystemの役目です。
systemはworldにたいして追加され毎フレームで実行されるみたいです。
RenderSystemに対してEntityを登録することで
毎フレームごとに値をRenderComponentとSpaceCpomponetの値を読み取り描画します.

worldに対してRenderSystemを追加します。
func (*myScene) Setup(world *ecs.World) { world.AddSystem(&common.RenderSystem{}) }
Gopher Entityを追加します。
はじめにGopher entityのstructの定義をします。
コンポーネントを持たせるには埋め込みを使うみたいですね。
コンポーネントの他にBasicEntityも一緒に埋め込む必要があるみたいですね。
type Gopher struct { ecs.BasicEntity common.RenderComponent common.SpaceComponent }
Gopherの実体を作成して
gopherのSpaseComponentを初期化します。位置や大きさを指定しています。
func (*myScene) Setup(world *ecs.World) { // worldに対してRenderSystemを追加 world.AddSystem(&common.RenderSystem{}) // gopherくんの初期化 // gopherくんのSpaceComponentの初期化。位置と大きさを設定する。 gopher := Gopher{BasicEntity: ecs.NewBasic()} gopher.SpaceComponent = common.SpaceComponent{ Position: engo.Point{10, 10}, Width: 303, Height: 641, } }
次にRenderComponentを初期化します。
preloadしていた画像をロードしRenderComponentのDrawableに設定します。
Scaleは多分倍率ですね。
func (*myScene) Setup(world *ecs.World) { // worldに対してRenderSystemを追加 world.AddSystem(&common.RenderSystem{}) // gopherくんの初期化 // gopherくんのSpaceComponentの初期化。位置と大きさを設定する。 gopher := Gopher{BasicEntity: ecs.NewBasic()} gopher.SpaceComponent = common.SpaceComponent{ Position: engo.Point{10, 10}, Width: 303, Height: 641, } // gopherくんのRenderComponentにpreLoadしていた画像を設定する texture, err := common.LoadedSprite("textures/gopher.png") if err != nil { log.Println("Unable to load texture: " + err.Error()) } gopher.RenderComponent = common.RenderComponent{ Drawable: texture, Scale: engo.Point{1, 1}, } }
最後にgopherくんをworldに追加します。
func (*myScene) Setup(world *ecs.World) { // worldに対してRenderSystemを追加 world.AddSystem(&common.RenderSystem{}) // gopherくんの初期化 // gopherくんのSpaceComponentの初期化。位置と大きさを設定する。 gopher := Gopher{BasicEntity: ecs.NewBasic()} gopher.SpaceComponent = common.SpaceComponent{ Position: engo.Point{10, 10}, Width: 303, Height: 641, } // gopherくんのRenderComponentにpreLoadしていた画像を設定する texture, err := common.LoadedSprite("textures/gopher.png") if err != nil { log.Println("Unable to load texture: " + err.Error()) } gopher.RenderComponent = common.RenderComponent{ Drawable: texture, Scale: engo.Point{1, 1}, } // WorldのRenderSystemにgopherを登録 for _, system := range world.Systems() { switch sys := system.(type) { case *common.RenderSystem: sys.Add(&gopher.BasicEntity, &gopher.RenderComponent, &gopher.SpaceComponent) } } }
完成したコード
最後に完成したコードを貼っておきます。
実行すると、Gopherくんが表示されます。
次回は、キーボードからの入力を受け取ってGopherくんを動かせるようにしてみようと思います。

ソースコードはこちらから↓
package main import ( "log" "engo.io/ecs" "engo.io/engo" "engo.io/engo/common" ) type myScene struct{} // Gopher gopherくんのstruct定義 type Gopher struct { ecs.BasicEntity common.RenderComponent common.SpaceComponent } // Type uniquely defines your game type func (*myScene) Type() string { return "myGame" } // Preload is called before loading any assets from the disk, // to allow you to register / queue them func (*myScene) Preload() { engo.Files.Load("textures/gopher.png") } // Setup メインループが開始する前に実行される関数 func (*myScene) Setup(world *ecs.World) { // worldに対してRenderSystemを追加 world.AddSystem(&common.RenderSystem{}) // gopherくんの初期化 // gopherくんのSpaceComponentの初期化。位置と大きさを設定する。 gopher := Gopher{BasicEntity: ecs.NewBasic()} gopher.SpaceComponent = common.SpaceComponent{ Position: engo.Point{10, 10}, Width: 303, Height: 641, } // gopherくんのRenderComponentにpreLoadしていた画像を設定する texture, err := common.LoadedSprite("textures/gopher.png") if err != nil { log.Println("Unable to load texture: " + err.Error()) } gopher.RenderComponent = common.RenderComponent{ Drawable: texture, Scale: engo.Point{1, 1}, } // WorldのRenderSystemにgopherを登録 for _, system := range world.Systems() { switch sys := system.(type) { case *common.RenderSystem: sys.Add(&gopher.BasicEntity, &gopher.RenderComponent, &gopher.SpaceComponent) } } } func main() { opts := engo.RunOptions{ Title: "Go lang のゲーム", Width: 400, Height: 400, } engo.Run(opts, &myScene{}) }