はじめに
先日、このゲームエンジンはアートであるという記事を読んで、go言語のことやebitenの設計思想を見ているうちに「これは自分に合ってるかもしれない!」と強く思い手を出してみた。しかしebitenはじめgo言語は触ったことがなく、とりあえず学習として画像の表示を関数化することを目標にした。
ebitenとは?
go言語で作られた汎用2Dゲームエンジンで、設計思想にある「ミニマムな API で実用的な 2D ゲームが作れるかどうか」の部分で、RigidChipsをやっていた頃を思い出し、今回手を出してみることにしました。
インストールとかチュートリアル
公式がとてもよくわかりやすいのでまずはこちらへ
サンプルの画像が回転するやつのソースコードを見てみる
公式のソースコードを見てみましょう(英語の解説の部分を日本語に翻訳してちょっとコメントを追加しています)
func (g *Game) Draw(screen *ebiten.Image) {
// 画像のサイズを取得
w, h := gophersImage.Size()
// 画像のオプションの準備
op := &ebiten.DrawImageOptions{}
// 画像の中心をスクリーンの左上に移動させる
// これは回転の準備で、 ジオメトリマトリックス(回転や移動の処理)が適用されるときの
// 原点が画面の左上だからである
op.GeoM.Translate(-float64(w)/2, -float64(h)/2)
// 画像を回転させる。結果的に回転のアンカーとなるポイントは画像の中心となる
op.GeoM.Rotate(float64(g.count%360) * 2 * math.Pi / 360)
// 画像をスクリーンの中心に持ってくる
op.GeoM.Translate(screenWidth/2, screenHeight/2)
// 画像を描画する
screen.DrawImage(gophersImage, op)
}
画像を任意の位置と角度で表示するためだけに、結構記述をする必要があります。
ここにこの設計思想である「ミニマムなAPI」というのが出てますね。基本的なパーツを用意して、自分で組み立てていく感じが本当にたまらんのです。
とはいえ、毎回画像を表示させるためだけにこれだけ書くのはしんどいので関数化して簡単に書けるようにしましょう。
ファイル構成
.
├── src
│ └── image
│ └── image.go
└── main.go
└── go.mod
└── go.sum
└── testicon.png (適当に用意してください)
完成したコード
package main
import (
"log"
// これはインポートしたときの効果を得るために_つけてる
_ "image/png"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
// github.com/username/projectname の部分はgo.modのmoduleを入れてください
"github.com/username/projectname/src/image"
)
// グローバルにimage使うためにここで宣言
var img *ebiten.Image
// これは一番最初に一度だけ呼ばれる
func init() {
//画像を読み込む
var err error
img, _, err = ebitenutil.NewImageFromFile("testicon.png")
if err != nil {
log.Fatal(err)
}
}
type Game struct{
// Gameオブジェクトにint型のcountを宣言する
count int
}
func (g *Game) Update() error {
// マイフレームごとにgを1つずつ増加させる
g.count++
return nil
}
func (g *Game) Draw(screen *ebiten.Image) {
// 画像をグルグル回転させながら表示させる
image.Show(screen, img, 0.1, 100, 100, float64(g.count%360))
// 画像を45度傾けた状態で表示させる
image.Show(screen, img, 0.1, 150, 150, 45)
}
func (g *Game) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) {
return 320, 240
}
func main() {
ebiten.SetWindowSize(640, 480)
ebiten.SetWindowTitle("Hello, World!")
if err := ebiten.RunGame(&Game{}); err != nil {
log.Fatal(err)
}
}
package image
import (
"math"
"github.com/hajimehoshi/ebiten/v2"
)
/*
引数解説
screen : *ebiten.Image 描画するスクリーン
img : *ebiten.Image 描画したい画像
coefficient : float64 画像の縮小/拡大の係数
x_coodinates : float64 画像のX座標
y_coodinates : float64 画像のY座標
angle : float64 画像の回転角度(degree 度 )
*/
func Show(screen *ebiten.Image, img *ebiten.Image, coefficient float64, x_coodinates float64, y_coodinates float64, angle float64) {
// 画像のサイズを取得
w, h := img.Size()
// 係数で画像を拡大/縮小したときの大きさを計算しておく
var sw, sh float64 = float64(w) * coefficient, float64(h) * coefficient
// オプションを宣言
op := &ebiten.DrawImageOptions{}
// 画像を拡大/縮小する
op.GeoM.Scale(coefficient, coefficient)
// 縮小したサイズに合わせて、画面の左上に縦横半分めり込む形にする
op.GeoM.Translate(-sw/2, -sh/2)
// 画像を画面の左上を中心に回転させる(縦横半分めり込んでいるので、中心で回転することになる)
op.GeoM.Rotate(angle / 180 * math.Pi)
// 好きな位置へ移動させる
op.GeoM.Translate(x_coodinates, y_coodinates)
// 画像を描画する
screen.DrawImage(img, op)
}
画像表示を切り出したおかげで、main.goの中で画像を描画する際には1行で済むようになりました。
importは絶対パスで書く
相対パスで書いて全然読み込まれない、、、と思ったら、絶対パスで書くらしいです
go.modに書いてあるモジュール名を使って、絶対パスを書いてください
参考:https://qiita.com/momotaro98/items/23fa4356389a7e610acc
おわりに
いろんなエフェクトのライブラリ作っていきたいです。
まじでrigidchips思い出すなぁ。。