20190405のGoに関する記事は4件です。

GAE+GO+Echoでgo1.11対応した

GAE+GO+Echoでgo1.11に対応した。

実はほとんど変更する箇所がないけれど最初のサーバー起動のところだけハマったのでメモ的に。
echoのバージョンは v3.3.10 で対応しています。

Echo公式のGAEへの対応方法はApp Engine Standard Go 1.9までのもので特に更新されてないっぽかった。
https://echo.labstack.com/cookbook/google-app-engine

ただ、上記のやり方でも動くしApp Engine APIを使い続ける場合もそのままで良い。

しかし、今回やりたかったのはApp Engine APIからの脱却。
簡単に言うと最後の

appengine.Main()

を外したい。

で、大したことじゃないので結論。
Go1.11からはgo標準のhttp.ListenAndServeで起動できるようになったので、こいつを使うだけで良い。

具体的には公式のコードのappengine.Main() の部分をhttp.ListenAndServe()にするだけ。

package main

import (
    "fmt"
    "log"
    "net/http"
    "os"

    "github.com/labstack/echo"
)

func createMux() *echo.Echo {
    e := echo.New()
    return e
}

func main() {
    //この辺は相変わらず必要
    e := echo.New()
    http.Handle("/", e)

    //ここから追加
    port := os.Getenv("PORT")
    if port == "" {
        port = "8080"
        log.Printf("Defaulting to port %s", port)
    }
    log.Printf("Listening on port %s", port)
    //この部分で起動
    log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", port), nil))
}

仕組みを理解してる人にとっては当然やんって感じかもしれないですね。

余談ですがFirestoreもGAEから使っていたのをFirebaseのものに変更しました。と言っても、FiresotreってGCPからアクセスしてもFirebaseからアクセスしても同じものを使ってるようでFirebaseの画面から連携処理をしたらそのまま同じデータが見れました。コードも少し書き換えるだけでできたので以前書いた記事に加筆するか新しい記事書くかも。

https://qiita.com/nyappa/items/edc9d8765e164254bf1a

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Google App EngineのHTTPレスポンスヘッダにContent-Typeを書き込む順序について

net/http の ResponseWriter へのレスポンスヘッダへの書き込みは、以下のようにWriteしたあとに変更しても影響はないとされている。

Changing the header map after a call to WriteHeader (or Write) has no effect unless the modified headers are trailers.
https://golang.org/src/net/http/server.go#L99

またGoogle App Engineのドキュメントには、BodyからContent-Typeを推定すると記載されている。

Content-Type
If you do not explicitly set this header, the http.ResponseWriter class detects the content type from the start of the response body, and sets the Content-Type header accordingly.
このヘッダーを明示的に設定しない場合は、http.ResponseWriter クラスがレスポンス本文の先頭からコンテンツ タイプを検出し、それに応じて Content-Type ヘッダーが設定されます。

しかし上記の仕様とは異なる結果となることがあり、
実行環境、Content-Typeを設定するタイミングHTTPメソッドによってResponseとして返ってくるContent-Typeが変わる。

ローカルで実行したときの4つの結果はすべて想定通りである。

想定外の挙動は以下の2つ。

  1. GAE(appdngine.Main)とGAE(http.ListenAndServe)どちらもGETのときはContent-Typeがapplication/jsonにならない。
  2. GAE(appdngine.Main) | Writeの後 | POST の場合に、Content-Typeがapplication/jsonになっている。
実行環境 Content-Typeを設定するタイミング HTTPメソッド Responseとして返ってくるContent-Type
ローカル Writeの前 GET application/json
ローカル Writeの前 POST application/json
ローカル Writeの後 GET text/plain; charset=utf-8
ローカル Writeの後 POST text/plain; charset=utf-8
GAE(appdngine.Main) Writeの前 GET text/html; charset=UTF-8
GAE(appdngine.Main) Writeの前 POST application/json
GAE(appdngine.Main) Writeの後 GET text/html; charset=UTF-8
GAE(appdngine.Main) Writeの後 POST application/json
GAE(http.ListenAndServe) Writeの前 GET text/html; charset=UTF-8
GAE(http.ListenAndServe) Writeの前 POST application/json
GAE(http.ListenAndServe) Writeの後 GET text/html; charset=UTF-8
GAE(http.ListenAndServe) Writeの後 POST text/plain; charset=utf-8

まだコードは読めていないが予想としては、UTF-8とutf-8と大文字、小文字のパターンが存在している。Go標準は小文字なので、大文字のときはGAEが書き換えている可能性がある。
つまりGAEでGETのときはresponseを書き換えられている。GAE(http.ListenAndServe) | Writeの後 | POST のときだけは、Go標準の挙動としてtext/plain; charset=utf-8が返っている。

検証コード

package main

import (
    "net/http"

    "google.golang.org/appengine"
)

// 正しい実装
func handler1(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    w.Write([]byte(`{"test": "hello"}`))
}
// 正しくない実装
func handler2(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte(`{"test": "hello"}`))
    w.Header().Set("Content-Type", "application/json")
}

func main() {
    http.HandleFunc("/1", handler1)
    http.HandleFunc("/2", handler2)

    // ローカル
    // http.ListenAndServe(":8080", nil)
    // GAE(appdngine.Main)
    // appengine.Main()
    // GAE(http.ListenAndServe)
    // http.ListenAndServe(fmt.Sprintf(":%s", os.Getenv("PORT")), nil) 
}

まだ実装の調査できてないけどいったんメモです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Golandで go mod使う時の注意

普段からGolandで開発してるんだけどプロジェクトでgo.modを使おうとして、コードは問題ないはずなのにimportをうまく認識してくれずに困ったのでメモ。

解決方法は単純でメニューのPreferencesから Go Modulesを有効にするだけ。
スクリーンショット 2019-04-05 11.48.33.png

go modは便利そうだけど、まだ情報が少なくて妙なところでハマりがち。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Go言語で作るマリオ風2Dゲーム

概要

Go言語で2Dのゲームアプリの作り方を調べたので、簡単なゲームをサンプルとして作りました。
こちらにソースコード一式があります。

作成したもの

以下の画像のように、マリオのようなゲームを作りました。
output.gif

緑の玉がプレーヤーで、青いお化けが敵です。
ステージ上にランダムに配置された落とし穴に落ちたり、ふわふわ動くお化けに当たったりしたら、ゲームオーバーです。

ゲームのステージは2種類あり、上のものの他にも、雪のステージもあります。
snow.gif

使用するライブラリについて

engoというライブラリを用いることで、クロスプラットフォームなデスクトップゲームアプリができます。
このライブラリを使用する上で必要となる基本的な概念を、以下で説明します。

ライブラリの基本的な概念

Entityとは

スクリーンに描画をされて、毎フレームごとに移動や当たり判定などの何らかの処理を行いたいものがある場合は、それらをEntityとして宣言をする必要があります。

私が作成したゲームだと、緑のプレーヤー青いお化けの敵、そして地面や草や木の3種類のエンティティをEntityとして登録しています。

Entityとして登録するには、以下のフィールドを保持する構造体を作ります。

type Sample struct {
    ecs.BasicEntity
    common.RenderComponent
    common.SpaceComponent
}

RenderComponentではEntityの見た目に関する情報を、SpaceComponentでは位置に関する情報を保持します。

Systemとは

上で説明したEntityを、Systemに登録をすることで、画面上に描画処理をしたり毎フレームごとになんらかの処理を行ったりできるようになります。

Systemを宣言するには、以下のフィールドを保持する構造体を作ります。

type SampleSystem struct {
    texture *common.Texture
    sampleEntity *Sample
    world *ecs.World
}

textureは見た目を定義するものであり、sampleEntityは上で説明したEntityを保持するものです。

そして、作成した構造体に以下の3つのメソッドを持たせます。

func (*SampleSystem) New(w *ecs.World){}
func (*SampleSystem) Remove(ecs.BasicEntity) {}
func (*SampleSystem) Update(dt float32) {}

New()Systemが作成された時に、Remove()は削除された時に、Update()は毎フレームに、それぞれ呼び出されるので、必要な処理を中に記述します。

通常New()では見た目の設定など初期設定を、Update()では移動や当たり判定などの処理を、それぞれ行います。

ゲームの作成

詳細なソースコードはGitHubにありますが、ここでは一部をかいつまんで説明します。

背景の作成

地面を描画します。
素材はここからとってきます。
tilesheet_grass.png
tilesheet_snow.png

この素材の一部をタイルのように画面に張り付けていきます。まずはEntitySystemの宣言です。

tileSystem.go
// Entity
type Tile struct {
    ecs.BasicEntity
    common.RenderComponent
    common.SpaceComponent
}

// System
type TileSystem struct {
    world *ecs.World
    // x軸座標
    positionX int
    // y軸座標
    positionY int
    tileEntity []*Tile
    texture *common.Texture
}

続いて、New()関数でこれらを描画をしていきます。

クリックしてコードを展開
tileSystems.go
func (ts *TileSystem) New(w *ecs.World){
    rand.Seed(time.Now().UnixNano())

    ts.world = w
    // 落とし穴作成中の状態を保持(0 => 作成していない、1以上 => 作成中)
    tileMakingState := 0
    // 雲の作成中の状態を保持 (0の場合:作成していない、奇数の場合:{(x+1)/2}番目の雲の前半を作成中、偶数の場合:{x/2}番目の雲の後半を作成中)
    cloudMakingState := 0
    // 雲の高さを保持
    cloudHeight := 0
    // タイルの作成
    tmp := rand.Intn(2)
    var loadTxt string
    // ランダムにステージを選ぶ
    if tmp == 0 {
        loadTxt = "tilemap/tilesheet_grass.png"
    } else {
        loadTxt = "tilemap/tilesheet_snow.png"
    }
    Spritesheet = common.NewSpritesheetWithBorderFromFile(loadTxt, 16, 16, 0, 0)
    Tiles := make([]*Tile, 0)
    for j := 0; j < 2800; j++ {
        // 地表の作成
        if (j > 10){
            if (tileMakingState > 1 && tileMakingState < 4){
                for t:= 0; t < 8; t++ {
                    FallPoint = append(FallPoint,j * 16 - t)
                }
            } else if (tileMakingState == 0){
                // すでに作成中でない場合、たまに落とし穴を作る
                randomNum := rand.Intn(10)
                if (randomNum == 0) {
                    FallStartPoint = append(FallStartPoint,j * 16)
                    tileMakingState = 1
                }
            }
        }
        // 描画するタイルを保持
        var selectedTile int
        // 描画するタイルを選択
        switch tileMakingState {
            case 0: selectedTile = 1
            case 1: selectedTile = 2
            case 2: tileMakingState += 1; continue
            case 3: tileMakingState += 1; continue
            case 4: selectedTile = 0
        }
        // タイルEntityの作成
        tile := &Tile{BasicEntity: ecs.NewBasic()}
        // 位置情報の設定
        tile.SpaceComponent.Position = engo.Point{
            X: float32(j * 16),
            Y: float32(237),
        }
        // 見た目の設定
        tile.RenderComponent.Drawable = Spritesheet.Cell(selectedTile)
        tile.RenderComponent.SetZIndex(0)
        Tiles = append(Tiles, tile)

        if (tileMakingState > 0){
            if (tileMakingState == 4){
                tileMakingState = 0
                continue
            }
            tileMakingState += 1
        }
    }
    for j := 0; j < 2800; j++ {
        // 雲の作成
        if (cloudMakingState == 0){
            randomNum := rand.Intn(6)
            if (randomNum < 7 && randomNum % 2 == 1) {
                cloudMakingState = randomNum
            }
            cloudHeight = rand.Intn(70) + 10
        }
        if (cloudMakingState != 0){
            // 雲Entityの作成
            cloudTile := cloudMakingState + 9
            cloud := &Tile{BasicEntity: ecs.NewBasic()}
            cloud.SpaceComponent.Position = engo.Point{
                X: float32(j * 16),
                Y: float32(cloudHeight),
            }
            cloud.RenderComponent.Drawable = Spritesheet.Cell(cloudTile)
            cloud.RenderComponent.SetZIndex(0)
            Tiles = append(Tiles, cloud)
            // 前半を作成中であれば、次は後半を作成する
            if (cloudMakingState % 2 == 1){
                cloudMakingState += 1
            } else {
                cloudMakingState = 0
            }
        }
        //草の作成
        if (!utils.Contains(FallPoint,j * 16)){
            // 落とし穴の上には作らない
            var grassTile int
            randomNum := rand.Intn(18)
            if (randomNum  < 6) {
                grassTile = 26 + randomNum
                grass := &Tile{BasicEntity: ecs.NewBasic()}
                grass.SpaceComponent.Position = engo.Point{
                    X: float32(j * 16),
                    Y: float32(221),
                }
                grass.RenderComponent.Drawable = Spritesheet.Cell(grassTile)
                grass.RenderComponent.SetZIndex(1)
                Tiles = append(Tiles, grass)

            }
        }

    }
    // 地面の描画
    for i := 0; i < 3; i++ {
        tileMakingState = 0
        for j := 0; j < 2800; j++ {
            if (tileMakingState == 0){
                // 落とし穴を作る場合
                if (utils.Contains(FallStartPoint,j * 16)){
                    tileMakingState = 1
                }
            }
            // 描画するタイルを保持
            var selectedTile int
            // 描画するタイルを選択
            switch tileMakingState {
                case 0: selectedTile = 17
                case 1: selectedTile = 18
                case 2: tileMakingState += 1; continue
                case 3: tileMakingState += 1; continue
                case 4: selectedTile = 16
            }
            tile := &Tile{BasicEntity: ecs.NewBasic()}
            tile.SpaceComponent.Position = engo.Point{
                X: float32(j * 16),
                Y: float32(285 - i * 16),
            }
            tile.RenderComponent.Drawable = Spritesheet.Cell(selectedTile)
            tile.RenderComponent.SetZIndex(0)
            Tiles = append(Tiles, tile)

            if (tileMakingState > 0){
                if (tileMakingState == 4){
                    tileMakingState = 0
                    continue
                }
                tileMakingState += 1
            }
        }
    }
    tileMakingState = 0
    for _, system := range ts.world.Systems() {
        switch sys := system.(type) {
        case *common.RenderSystem:
            for _, v := range Tiles {
                ts.tileEntity = append(ts.tileEntity, v)
                sys.Add(&v.BasicEntity, &v.RenderComponent, &v.SpaceComponent)
            }
        }
    }
}

乱数を発生させて、ランダムで落とし穴や草、雲を作成しています。

敵の作成

敵のお化けを作ります。
お化けの画像はこちらからとってきました。
pipo-halloweenchara2016_19.png

まずはEntityとSystemを宣言します。

enemySystem.go
type Enemy struct {
    ecs.BasicEntity
    common.RenderComponent
    common.SpaceComponent
    // ジャンプの状態(0 => 着地中, 1 => 1ジャンプ中, 2 => 降下中)
    jumpState int
    // ジャンプの残り時間
    jumpDuration int
    // 移動の速度(0 ~ 2, 数値が高いほど早い)
    velocity int
    // 画面から消えているか
    ifDissappearing bool
}

type EnemySystem struct {
    world *ecs.World
    enemyEntity []*Enemy
    texture *common.Texture
}

続いて、New()関数で描画と配置を行います。

クリックしてコードを展開
enemySystem.go
func (es *EnemySystem) New(w *ecs.World){
    es.world = w
    Enemies := make([]*Enemy, 0)
    // ランダムで配置
    for i := 0; i < 44800; i++ {
        randomNum := rand.Intn(400)
        if (randomNum == 0){
            // 敵の作成
            enemy := Enemy{BasicEntity: ecs.NewBasic()}
            enemy.SpaceComponent = common.SpaceComponent{
                Position: engo.Point{X:float32(i),Y:float32(212)},
                Width: 30,
                Height: 30,
            }
            // 画像の読み込み
            texture, err := common.LoadedSprite("pics/ghost.png")
            if err != nil {
                fmt.Println("Unable to load texture: " + err.Error())
            }
            enemy.RenderComponent = common.RenderComponent{
                Drawable: texture,
                Scale: engo.Point{X:1.1, Y:1.1},
            }
            enemy.RenderComponent.SetZIndex(1)
            es.texture = texture
            for _, system := range es.world.Systems() {
                switch sys := system.(type) {
                case *common.RenderSystem:
                    sys.Add(&enemy.BasicEntity, &enemy.RenderComponent, &enemy.SpaceComponent)
                }
            }
            enemy.velocity = rand.Intn(3)
            Enemies = append(Enemies,&enemy)
        }
        es.enemyEntity = Enemies
    }
}

乱数を発生させて、ステージ上のランダムな位置にお化けを発生させます。

そしてUpdate()関数で、作成されたお化けを移動させます。

クリックしてコードを展開
enemySystem.go
func (es *EnemySystem) Update(dt float32) {
    // カメラとプレーヤーの位置を取得
    var cameraPosition float32
    var playerPositionX float32
    for _, system := range es.world.Systems() {
        switch sys := system.(type) {
        case *common.CameraSystem:
            cameraPosition = sys.X()
        case *PlayerSystem:
            playerPositionX = sys.playerEntity.SpaceComponent.Position.X
        }
    }
    for _, o := range es.enemyEntity{
        // 画面に描画されていないオブジェクトは移動処理をしない
        if (o.SpaceComponent.Position.X > cameraPosition - 240 && o.SpaceComponent.Position.X < cameraPosition + 200 && !o.ifDissappearing){
            // プレーヤーとの当たり判定
            if (o.SpaceComponent.Position.X == playerPositionX) {
                for _, system := range es.world.Systems() {
                    switch sys := system.(type) {
                    case *PlayerSystem:
                        sys.playerEntity.damage += 1
                    }
                }
            }
            o.SpaceComponent.Position.X -= float32(o.velocity + 1)
            // ジャンプをしていない場合
            if (o.jumpState == 0){
                o.jumpState = rand.Intn(2) + 1
                jumpTemp := rand.Intn(3)
                switch (jumpTemp) {
                    case 0: o.jumpDuration = 15
                    case 1: o.jumpDuration = 25
                    case 2: o.jumpDuration = 35
                }
            }
            // ジャンプ処理
            if (o.jumpState == 1){
                // ジャンプをし終わっていない場合
                if (o.jumpDuration > 0){
                    o.SpaceComponent.Position.Y -= 3
                    o.jumpDuration -= 1
                } else {
                    // ジャンプをし終わった場合
                    o.jumpState = 2
                }
            } else {
                // 降下をし終わっていない場合
                if (o.SpaceComponent.Position.Y < 212){
                    o.SpaceComponent.Position.Y += 3
                } else {
                    // 降下し終わった場合
                    o.jumpState = 0
                }
            }
        }else if (o.ifDissappearing){
            o.SpaceComponent.Position.Y += 3
        }
    }
}

ランダムな高さのジャンプを繰り返しながら、ランダムな速度で移動をさせます。

上にCameraSystemと出てきますが、これはゲーム内の視点を動かすために、ライブラリで最初から用意されているSystemです。

プレーヤーの作成

プレーヤーのEntitySystemを宣言します。

playerSystem.go
type Player struct {
    ecs.BasicEntity
    common.RenderComponent
    common.SpaceComponent
    // ジャンプの時間
    jumpDuration int
    // カメラの進んだ距離
    distance int
    // 落ちているかどうか
    ifFalling bool
    // ダメージ
    damage int
}

type PlayerSystem struct {
    world *ecs.World
    playerEntity *Player
    texture *common.Texture
}

New()関数で描画をします。

クリックしてコードを展開
playerSystem.go
func (ps *PlayerSystem) New(w *ecs.World){
    ps.world = w
    // プレーヤーの作成
    player := Player{BasicEntity: ecs.NewBasic()}

    // 初期の配置
    positionX := int(engo.WindowWidth() / 2)
    positionY := int(engo.WindowHeight() - 88)
    player.SpaceComponent = common.SpaceComponent{
        Position: engo.Point{X:float32(positionX),Y:float32(positionY)},
        Width: 30,
        Height: 30,
    }
    // 画像の読み込み
    texture, err := common.LoadedSprite("pics/greenoctocat.png")
    if err != nil {
        fmt.Println("Unable to load texture: " + err.Error())
    }
    player.RenderComponent = common.RenderComponent{
        Drawable: texture,
        Scale: engo.Point{X:0.1, Y:0.1},
    }
    player.RenderComponent.SetZIndex(1)
    ps.playerEntity = &player
    ps.texture = texture
    for _, system := range ps.world.Systems() {
        switch sys := system.(type) {
        case *common.RenderSystem:
            sys.Add(&player.BasicEntity, &player.RenderComponent, &player.SpaceComponent)
        }
    }
    common.CameraBounds = engo.AABB{
        Min: engo.Point{X: 0, Y: 0},
        Max: engo.Point{X: 40000, Y: 300},
    }
}

Update()関数で、移動をします。

クリックしてコードを展開
playerSystem.go
func (ps *PlayerSystem) Update(dt float32) {
    // ダメージが1であればゲームを終了
    if ps.playerEntity.damage > 0 {
        whenDied(ps)
    }
    // 落とし穴
    if (ps.playerEntity.jumpDuration == 0 && utils.Contains(FallPoint,int(ps.playerEntity.SpaceComponent.Position.X)) ){
        ps.playerEntity.ifFalling = true
        ps.playerEntity.SpaceComponent.Position.Y += 5
    }
    // 穴に落ち切ったらライフを0にする
    if ps.playerEntity.SpaceComponent.Position.Y > 300 {
        ps.playerEntity.damage += 1
    }

    if(!ps.playerEntity.ifFalling){
    // 右移動
    if engo.Input.Button("MoveRight").Down()  { 
        // 画面の真ん中より左に位置していれば、カメラを移動せずプレーヤーを移動する
        if (int(ps.playerEntity.SpaceComponent.Position.X) < ps.playerEntity.distance + int(engo.WindowWidth()) / 2){
            ps.playerEntity.SpaceComponent.Position.X += 5
        } else {
            // 画面の右端に達していなければプレーヤーを移動する
            if (int(ps.playerEntity.SpaceComponent.Position.X) < ps.playerEntity.distance + int(engo.WindowWidth()) - 10){
                ps.playerEntity.SpaceComponent.Position.X += 5
            }
            // カメラを移動する
            engo.Mailbox.Dispatch(common.CameraMessage{
                Axis:        common.XAxis,
                Value:       5,
                Incremental: true,
            })
            ps.playerEntity.distance += 5
        }
    }
    // プレーヤーを左に移動
    if engo.Input.Button("MoveLeft").Down()  {
        if int(ps.playerEntity.SpaceComponent.Position.X) > ps.playerEntity.distance + 10{
            ps.playerEntity.SpaceComponent.Position.X -= 5
        }
    }
    // プレーヤーをジャンプ
    if engo.Input.Button("Jump").JustPressed() {
        if ps.playerEntity.jumpDuration == 0 {
            ps.playerEntity.jumpDuration = 1
        }
    }
    if ps.playerEntity.jumpDuration != 0 {
        ps.playerEntity.jumpDuration += 1
        if ps.playerEntity.jumpDuration < 14 {
            ps.playerEntity.SpaceComponent.Position.Y -= 5
        } else if ps.playerEntity.jumpDuration < 26 {
            ps.playerEntity.SpaceComponent.Position.Y += 5
        } else {
            ps.playerEntity.jumpDuration = 0
        }
    }
    }
}

移動をするだけでなく、落とし穴に落ちたらゲームオーバーにする、などの処理も行なっています。

ゲームの開始

上で作成したSystemなどを用いて、ゲームを動かします。

ゲームプログラムのメインの部分は、以下のようになります。

game.go
package main

import (
    "bytes"
    "engo.io/engo"
    "engo.io/engo/common"
    "engo.io/ecs"
    "image/color"
    "golang.org/x/image/font/gofont/gosmallcaps"
    "./systems"
)

type myScene struct {}

func (*myScene) Type() string { return "myGame" }

func (*myScene) Preload() {
    // 必要なファイルを事前に読み込んでおく
    engo.Files.Load("pics/greenoctocat.png", "pics/ghost.png", "tilemap/tilesheet_grass.png", "tilemap/tilesheet_snow.png")
    engo.Files.LoadReaderData("go.ttf", bytes.NewReader(gosmallcaps.TTF))
    common.SetBackground(color.RGBA{255, 250, 220, 0})
}

func (*myScene) Setup(u engo.Updater){
    engo.Input.RegisterButton("MoveRight", engo.KeyD, engo.KeyArrowRight)
    engo.Input.RegisterButton("MoveLeft", engo.KeyA, engo.KeyArrowLeft)
    engo.Input.RegisterButton("Jump", engo.KeySpace)
    world, _ := u.(*ecs.World)
    // Systemの追加
    world.AddSystem(&common.RenderSystem{})
    world.AddSystem(&systems.TileSystem{})
    world.AddSystem(&systems.PlayerSystem{})
    world.AddSystem(&systems.EnemySystem{})
}

func (*myScene) Exit() {
    engo.Exit()
}

func main(){
    opts := engo.RunOptions{
        Title:"myGame",
        Width:400,
        Height:300,
        StandardInputs: true,
        NotResizable:true,
    }
    engo.Run(opts,&myScene{})
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む