Go言語でゲームを作りたい

たまにはゲームでも作ろうよ

あーゲームが作りたいですね。
僕は普段webサービスのコードしか書かないのですが、
たまにはゲームとかのコーディングもしたいですよね。

ゲームのコーディングってプログラミングの勉強に最適だと思うんですよね。
Goでゲームを作りたーい!

いい感じのGoのゲーム用パッケージをみつけてきました。

この記事の対象者

Goの環境を自力で作れて、チュートリアルぐらいはやったことがある人です。
それ以外の人は下のリンクからGoの世界に入門してください。
golang-jp.org

engo


Golangの2Dゲームエンジンです。
GitHubで公開されています。

engo.io

今回はengoのチュートリアルをやってみたいと思います。

windowを表示する

今回は
$GOPATH/src/GoGame
というディレクトリーを作って作業をしていきたいと思います。

Scene(シーン)
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を追加します。

Sceneは必ず一つのWorldを持っていてworldはいくつかのentityやsystemを持っています。
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{})
}