- 投稿日:2019-09-15T21:55:28+09:00
【Go】文字列をハッシュ化
関連
サンプルコード
package main import ( "crypto/md5" "crypto/sha1" "crypto/sha256" "crypto/sha512" "encoding/hex" "fmt" "golang.org/x/crypto/ripemd160" ) func main() { s := "password" b := []byte(s) md5 := md5.Sum(b) sha1 := sha1.Sum(b) sha256 := sha256.Sum256(b) sha384 := sha512.Sum384(b) sha512 := sha512.Sum512(b) ripemd160 := ripemd160.New().Sum(b) // 出力したいだけなら`fmt.Printf("%x\n", md5)`とすればよい // 文字列として取得したい場合は`hex.EncodeToString`を使用する fmt.Println("md5: " + hex.EncodeToString(md5[:])) fmt.Println("sha1: " + hex.EncodeToString(sha1[:])) fmt.Println("sha256: " + hex.EncodeToString(sha256[:])) fmt.Println("sha384: " + hex.EncodeToString(sha384[:])) fmt.Println("sha512: " + hex.EncodeToString(sha512[:])) fmt.Println("ripemd160: " + hex.EncodeToString(ripemd160[:])) // => md5: 5f4dcc3b5aa765d61d8327deb882cf99 // sha1: 5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8 // sha256: 5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8 // sha384: a8b64babd0aca91a59bdbb7761b421d4f2bb38280d3a75ba0f21f2bebc45583d446c598660c94ce680c47d19c30783a7 // sha512: b109f3bbbc244eb82441917ed06d618b9008dd09b3befd1b5e07394c706a8bb980b1d7785e5976ec049b46df5f1326af5a2ea6d103fd07c95385ffab0cacbc86 // ripemd160: 70617373776f72649c1185a5c5e9fc54612808977ee8f548b2258d31 }参考
- 投稿日:2019-09-15T17:36:24+09:00
Golang - ポリモーフィズム
概要
インターフェースを使ったポリモーフィズムについてまとめる
必要な前提知識は以下の二つ(もし分からなければこちらの記事の上の方を参照ください)
・インターフェースの定義の仕方
・インターフェースを実装する方法メリットは
ロジック内にはインターフェースしか現れないことインターフェース とは
インターフェースは言葉通り、境界面のイメージ
つまり、外から見た時に表面に何があるかを表しているGolangではインターフェースはメソッドの一覧で定義されているので
外からはこのメソッド一覧が見えて、そのメソッド一覧を使う事ができるポリモーフィズム とは
ある一つのインターフェースを実装した複数のオブジェクトに対して、そのインターフェースで公開されたメソッドで共通の処理をできること
開発者はインターフェースに命令することを意識すれば良い
どの構造体がこのインターフェースを実装したかは考えなくて良い下のコードでは、猫か犬のどちらが来たかを判断して名前を判定している
変数の型指定やメソッドを呼ぶ時には、インターフェースのIAnimalしか意識していない事が分かる
具体的なDogとCatを意識するのは、それらを生成するときだけであるpackage main import ( "fmt" ) type IAnimal interface { GetName() string } type Dog struct { name string } func (d Dog) GetName() string { return d.name } type Cat struct { name string } func (c Cat) GetName() string { return c.name } func main() { var animals []IAnimal = []IAnimal{ Dog{"ポチ"}, Cat{"ミケ"}, Cat{"タマ"}, } for _, animal := range animals { fmt.Println(animal.GetName()) } }まとめ
インターフェースを実装する事で、ロジック内にインターフェースしか現れない事が分かった
補足1
今回はインターフェースを実装した場合でポリモーフィズムを考えたが、継承に関してもポリモーフィズムは適用できる
仮に構造体PoodleをDogから継承させた場合もPoodle型はインターフェースであるIAnimalとして扱う事ができる補足2
今回は作らなかったがICat、IDog、IPoodleなどのインターフェースを作る事で
ICatを実装もしくは継承したオブジェクトだけが使える機能をICatで公開することもできる
インターフェースが役割を決め、それを実装や継承したものはその役割を持つという事が考えられる
- 投稿日:2019-09-15T17:09:11+09:00
Protocol Buffers / Go Generated Code(和訳)
このページは Protocol Buffers 公式リファレンス Go Generated Codeの和訳です。原文はCreative Commons Attribution 4.0 Licenseで公開されており、ソースコードはApache Licenseで公開されています。この訳文もそれにならいます。
Go Generated Code
このページでは、プロトコルバッファーコンパイラが、任意のプロトコル定義に対し。どのようなGoのコードを生成するかについて、正確に述べています。proto2とprot3の違いは明示されています(これらの違いはこのドキュメントに書かれているように生成されたコードにあり、ベースAPIにはありません。API上は二つのバージョンに違いはありません)。このドキュメントの前にproto2/proto3の言語仕様ガイドを読むことをお勧めします。
コンパイラ呼び出し
プロトコルバッファーコンパイラがGoのコードを生成するにはプラグインが必要です。 次のようにインストールすると、
$ go get github.com/golang/protobuf/protoc-gen-go
protoc
にコマンドラインフラグとして-go_out
を渡して実行した際に使われるprotoc-gen-go
のバイナリが入ります。--go_out
フラグで、コンパイラにGoのソースファイルを書き込む場所を指定します。 コンパイラは各.proto
ファイルの入力に対して、単一のソースファイルを作成します。出力されるファイル名は.protoファイルの名前と以下の二つの変更によって決定されます。
.proto
拡張子は.pb.go
に置き換えられます。例えば、player_record.proto
というファイルはplayer_record.pb.go
という名前になります。- (
--proto_path
、もしくは-I
で指定された)protoパスは、(--go_out
で指定された)ソース出力先に置き換えられます。次のようにプロトコンパイラを実行すると
protoc --proto_path=src --go_out=build/gen src/foo.proto src/bar/baz.protoコンパイラは
src/foo.proto
とsrc/bar/baz.proto
から、build/gen/foo.pb.go
とbuild/gen/bar/baz.pb.go
を生成します。必要であれば、コンパイラは
build/gen/bar
ディレクトリを自動生成しますが、build
やbuild/gen
は生成しません。これらは予め作成しておく必要があります。パッケージ
.proto
ファイルがパッケージ宣言を含んでいる場合、生成されたコードはprotoにあるpackage
をGoのパッケージ名として使います。その際まず.
を_
に変換します。例えば、protoのパッケージ名がexample.high_score
であれば、Goのパッケージ名はexample_high_score
になります。
.proto
ファイル内にgo_package
optionを入れることで、特定の.proto
に生成されるデフォルトのパッケージ名を上書きすることができます。例えば、.proto
ファイルに次のように書かれているとすると、package example.high_score; option go_package = "hs";
hs
というGoのパッケージ名が生成されます。他にありうるケースとして、
.proto
ファイルにパッケージ宣言が含まれていない場合、生成されたコードはファイル名(から拡張子を除いたもの)をGoのパッケージ名にします。その際、まず.
は_
に変換されます。例えば、パッケージ宣言のないhigh.score.proto
という名前のprotoパッケージは、high.score.pb.go
というファイルのhigh_score
というパッケージで出力されます。メッセージ型
次のようなシンプルなメッセージ宣言があるとします。
message Foo {}プロトコルバッファコンパイラは
Foo
という構造体を生成します。*Foo
はMessageインターフェースを実装します。詳細についてはインラインコメントで説明します。type Foo struct { } // Reset はprotoの状態をデフォルト値にします。 func (m *Foo) Reset() { *m = Foo{} } // String は、protoを文字列表現にして返します。 func (m *Foo) String() string { return proto.CompactTextString(m) } // ProtoMessage はprotoのMessageであることを明示的に示すために付加されており、 // 誰かが誤ってproto.Messageインターフェースを実装しないようにします func (*Foo) ProtoMessage() {}これらのメンバメソッドは常に存在します。また、
optimize_for
オプションをつけても、Goでは生成コードは変わりません(訳注:Java/C++では最適化されます)。ネストされた型
メッセージ型は他のメッセージ型の中に宣言することもできます。例を示します。
message Foo { message Bar { } }この場合、コンパイラは二つの構造体を生成します。
Foo
とFoo_Bar
です。Well-known types
プロトコルバッファには、 well-known types (WKT)と呼ばれる、一連の定義済みメッセージが付属しています。これの型は、他のサービスとの相互運用性のためにも、あるいは単純によくある有用なパターンを簡潔に表現するためにも使えます。例えば、Structメッセージ型は任意のC言語風の構造体を表します。
WKT用に事前生成されたGoのコードが、Go Protobuf ライブラリ の一部として配布されており、WKTを使うと、あなたが定義したメッセージ型から生成されたコードからも参照されます。例えば、このようにメッセージ型を定義したとします。
import "google/protobuf/struct.proto" import "google/protobuf/timestamp.proto" message NamedStruct { string name = 1; google.protobuf.Struct definition = 2; google.protobuf.Timestamp last_modified = 3; }生成されるGoのコードは次のようになるでしょう。
import google_protobuf "github.com/golang/protobuf/ptypes/struct" import google_protobuf1 "github.com/golang/protobuf/ptypes/timestamp" ... type NamedStruct struct { Name string Definition *google_protobuf.Struct LastModified *google_protobuf1.Timestamp }大抵の場合、コード内でこれらの型を直接インポートする必要はないでしょう。ただ、もし直接これらの型を参照する必要があれば、単純に
github.com/golang/protobuf/ptypes/[TYPE]
パッケージをインポートして、普通に使うだけです。フィールド
プロトコルバッファコンパイラはメッセージ型の中に定義されたそれぞれのフィールドに対応する構造体のフィールドを生成します。その性質が実際にどのようになるかは、フィールドの型や単数、配列、マップ、oneofのうちの何であるかによって異なります。
Goのフィールド名は必ずキャメルケースで生成されます。
.proto
ファイル内でフィールド名がこのようにスネークケースでで記述されていても同じです。大文字と小文字の変換は次のような流れで機能します。
先頭文字はエクスポートするため大文字になります。先頭文字がアンダースコアの場合は削除され、代わりに大文字の
X
が前置されます。先頭文字以外でアンダースコアが小文字に続く場合、アンダースコアは削除され、それに続く小文字は大文字に変換されます。
したがって、
foo_bar_baz
というprotoのフィールドはFooBarBaz
となり、_my_field_name_2
はXMyFieldName_2
となります。単数スカラー値フィールド (proto2)
下のフィールド定義のどちらの場合にも
optional int32 foo = 1; required int32 foo = 1;コンパイラは
Foo
という名前の*int32
型のフィールドと、GetFoo()
というメソッドを持った構造体を生成します。GetFoo()
はint32
値か、フィールドに値が設定されていない場合はデフォルト値を返します。デフォルト値が明示的に設定されていない場合は、その型のゼロ値を返します(数値であれば0、文字列であれば空文字になります)。(
bool
bytes
string
といった)他のスカラー型のフィールドでは、上記の*int32
の部分はスカラー値型テーブル上で対応するGoの型に置き換えられます。単数形スカラー値フィールド (proto3)
このフィールド定義の場合
int32 foo = 1;コンパイラは
Foo
という名前の*int32
型のフィールドと、GetFoo()
というメソッドを持った構造体を生成します。GetFoo()
はint32
値か、フィールドに値が設定されていない場合はデフォルト値を返します。デフォルト値が明示的に設定されていない場合は、その型のゼロ値を返します(数値であれば0、文字列であれば空文字になります)。(
bool
bytes
string
といった)他のスカラー型のフィールドでは、上記の*int32
の部分はスカラー値型テーブル上で対応するGoの型に置き換えられます。proto内で未設定の値は、その型のゼロ値として表現されます(数値であれば0、文字列であれば空文字になります)。単数形メッセージ型フィールド
あるメッセージ型が定義され、
message Bar {}
Bar
フィールドを持ったメッセージ型に対しては、// proto2 message Baz { optional Bar foo = 1; // optionalでなくrequiredでも生成コードは同じ } // proto3 message Baz { Bar foo = 1; }コンパイラは次のようなGoの構造体を生成します。
type Baz struct { Foo *Bar }メッセージ型フィールドは
nil
の場合があります。この場合フィールドは設定されておらず、実際は空の値です。これは空の
メッセージ型となる構造体の空のインスタンスを値に設定した場合とは異なります。またコンパイラはヘルパー関数として
func (m *Baz) GetFoo() *Bar
を生成します。このおかげで、途中のnil
チェックなしに参照呼び出しを連鎖させることができます。repeatedフィールド
各
repeated
フィールドは、Goの構造体内の該当するフィールドの要素型がT
であるとき、フィールドにT
のスライスを生成します。
repeated
フィールドを持ったこのメッセージの場合、message Baz { repeated Bar foo = 1; }コンパイラは以下のような構造体を生成します。
type Baz struct { Foo []*Bar }同様にフィールド定義が、
repeated bytes foo = 1;
であった場合、コンパイラはFoo
という名前の[][]byte
フィールドを持ったGoの構造体を生成します。repeated enumの場合、repeated MyEnum bar = 2;
に対して、コンパイラはBar
という名前の[]MyEnum
フィールドを持った構造体を生成します。mapフィールド
各mapフィールドにおいて、
TKey
がフィールドのキーとなる型で、TValue
がフィールドの値となる型である場合、コンパイラはGoの構造体に、map[TKey]TValue
型のフィールドを生成します。
map
フィールドを持ったこのメッセージの場合、message Bar {} message Baz { map<string, Bar> foo = 1; }コンパイラは次のようなGo構造体を生成します。
type Baz struct { Foo map[string]*Bar }oneof フィールド
oneof フィールドの場合、プロトコルバッファコンパイラは
isMessageName_MyField
というインターフェースのフィールドをひとつ生成します。またoneof
の中のそれぞれの単数フィールドに相当する構造体を生成します。これらは全てこのisMessageName_MyField
インターフェースを実装します。oneofフィールドを持ったこのメッセージの場合、
package account; message Profile { oneof avatar { string image_url = 1; bytes image_data = 2; } }コンパイラはt次の構造体を生成します。
type Profile struct { // Avatarに代入できる型: // *Profile_ImageUrl // *Profile_ImageData Avatar isProfile_Avatar `protobuf_oneof:"avatar"` } type Profile_ImageUrl struct { ImageUrl string } type Profile_ImageData struct { ImageData []byte }
*Profile_ImageUrl
と*Profile_ImageData
のどちらも、空のisProfile_Avatar()
メソッドを提供することで、isProfile_Avatar
を満たしています。次の例でどのようにしてフィールドに値を入れるかを示します。
p1 := &account.Profile{ Avatar: &account.Profile_ImageUrl{"http://example.com/image.png"}, } // imageDataは[]byteです imageData := getImageData() p2 := &account.Profile{ Avatar: &account.Profile_ImageData{imageData}, }フィールドにアクセスするには、値の型によるswitch文を使うことで、メッセージ型ごとに処理することができます。
switch x := m.Avatar.(type) { case *account.Profile_ImageUrl: // x.ImageUrlを使ってURLからくProfile画像を読み込む case *account.Profile_ImageData: // x.ImageDataを使ってbyte配列からProfile画像を読み込む case nil: // フィールドがない default: return fmt.Errorf("Profile.Avatarに未知の型が割り当てられています %T", x) }またコンパイラは
func (m *Profile) GetImageUrl() string
とfunc (m *Profile) GetImageData() []byte
というメソッドも生成します。それぞれのget関数はフィールドの値か、もし未設定であればゼロ値を返します。列挙型
次のような列挙型が与えられた場合、
message SearchRequest { enum Corpus { UNIVERSAL = 0; WEB = 1; IMAGES = 2; LOCAL = 3; NEWS = 4; PRODUCTS = 5; VIDEO = 6; } Corpus corpus = 1; ... }プロトコルバッファコンパイラは型とその型の一連の定数を生成します。
(上記のような)メッセージ型の中に列挙型がある場合、型の名前はメッセージ型の名前から始まります。
type SearchRequest_Corpus int32パッケージレベルの列挙型の場合、
enum Foo { DEFAULT_BAR = 0; BAR_BELLS = 1; BAR_B_CUE = 2; }Goの型の名前はprotoのenumの名前と同じです。
type Foo int32この型にはその値のラベルを返す
String()
メソッドがあります。
Enum()
メソッド1は、その値に割り当てられたメモリを初期化し、対応するポインタを返します。func (Foo) Enum() *Fooプロトコルバッファコンパイラはそれぞれの列挙型の値に対して定数を生成します。メッセージ内に列挙型がある場合、定数名の頭に親のメッセージ型の名が付きます。
const ( SearchRequest_UNIVERSAL SearchRequest_Corpus = 0 SearchRequest_WEB SearchRequest_Corpus = 1 SearchRequest_IMAGES SearchRequest_Corpus = 2 SearchRequest_LOCAL SearchRequest_Corpus = 3 SearchRequest_NEWS SearchRequest_Corpus = 4 SearchRequest_PRODUCTS SearchRequest_Corpus = 5 SearchRequest_VIDEO SearchRequest_Corpus = 6 )パッケージレベルの列挙型では、定数は代わりに列挙型の名前から始まります。
const ( Foo_DEFAULT_BAR Foo = 0 Foo_BAR_BELLS Foo = 1 Foo_BAR_B_CUE Foo = 2 )プロトコルバッファコンパイラは、整数値をキーにラベルの文字列の名前を値にしたmapと、ラベルの文字列をキーに整数値を値にしたmapを生成します。
var Foo_name = map[int32]string{ 0: "DEFAULT_BAR", 1: "BAR_BELLS", 2: "BAR_B_CUE", } var Foo_value = map[string]int32{ "DEFAULT_BAR": 0, "BAR_BELLS": 1, "BAR_B_CUE": 2, }.protoの記法では、複数の列挙型のラベルが同じ数の値を持つことができます。同じ値を持つラベルは同義語です。Goではこれらはきっかり同じように表現されます。すなわち同じ数値に複数の名前が対応するようになります。数値からラベルを逆引きするmapでは.protoで最初に現れるラベルだけが使われます。
拡張機能 (proto2)
拡張機能はproto2にのみ存在します。 proto2拡張のGo生成コードAPIのドキュメントについては、protoパッケージのドキュメントを参照してください。
Service
GoのコードジェネレータはデフォルトではServiceを生成しません。 gRPCプラグインを有効にすると(gRPC Goクイックスタートガイドを参照)、gRPCをサポートするコードが生成されます。
v1.3.2現在、列挙型で生成されるコードにEnum()メソッドが存在しない ↩
- 投稿日:2019-09-15T15:22:55+09:00
GolangのWebサーバで階層型アーキテクチャで行く場合、panicを使うと良いと思う
Don't panic!!
この言葉があることは十分に承知しています。
しかし、Golang標準ライブラリでも使っている箇所があります。
しかも、1ヶ所、2ヶ所では無いです。
(一番下にリンクした記事では、ざっくりgrepで、4000ヶ所らしいです。テストが含まれている気がして、正確性には疑問がありますが。。)
おそらく使うべき状況というものが存在するのだと思います。
(そもそも、絶対ダメなら言語仕様に無いはずです。)
なので、十分に見切って使う分には、使ったほうが良いと思うのです。
余談ですが、僕のことを好意的に思っているエンジニアでも、この話をすると引かれることが多いです。。。
使っちゃ駄目なパターン
Golang の defer 文と panic/recover 機構について
上記記事の引用:
- 外部に公開する API はエラーを伝える手段として panic() を使ってはいけない
- 多値の返り値 w/ error インターフェースを使うこと
- panic() はパッケージをまたいで伝搬させることがないようにする
- panic/recover 機構はスタックが深くなるような呼び出しをする際に有用
- 使用することで可読性を向上させることができる
つまり、外部にエラーを伝える手段としては error インターフェースを使うのが正式なやり方で、panic/recover はあくまで内部的に用いるべきもの、ということらしい。
Golangは全体的にエラーを返すやり方で記述されています。
なので、とあるライブラリだけ、panicを使って外部にエラーを返すのは、迷惑すぎるということだと思います。
逆に言えば、内部で使用する分には問題ないということです。
タイトルのWebサーバで階層型アーキテクチャの場合、階層分の
if error
が無くなって良いと思うのです。メリットについて
色々とメリットが多いです。
- エラー時にstacktraceの詳細なものが、必ず取れる
- Golang書いてびっくりしたのが、stacktrace頑張らないと出ないんですよね。。
if err != null
の記述が大幅に無くなるので、見通しが良くなる- メソッドチェインが出来るようになる
- エラーのreturn忘れが無くなる
- 1値返すメソッドか?、2値返すメソッドか?、あまり考えなくて良くなる
- 考えることが減るのと、コーディング量が減るので開発が速くなる
- どんなエラーが発生しても、必ず捉えられ後処理を実行できる
- データベースのTransaction、Rollbackとか
エラーのreturn忘れですが、知り合いのエンジニアからLinter使うでしょ?的な話がありました。
( staticcheck などのLinterを使えば指摘してくれます)
ですが、Linter無いと危なくて書けないとか、言語として間違っていると思います。
Golangはおそらく、Webサーバ用に作ってないのでしょうが、panic使えば回避出来るとか、例外的状況にも対応出来る言語だなぁと思います。
システム開発時に重要だと思うこと
僕はシステムは、結果を出してなんぼだと思っています。
大きな会社に居た時に、すごいメンバーがサービスを作って、売上出ないからクローズというのを、よく見かけました。
さらに最近の世の中は、変化が速く、変化よりも速い速度で製品を出せないと、出す頃には古くなっているという自体が起きます。
なので、システム開発に重要なのは、下記の2点になると思います。
* 正しく動作すること * 速く開発が出来ることこれを考えると、
- Golang
- Webサーバ
- 階層型アーキテクチャ
- panic解禁
というのは、かなり良い構成じゃないかなぁと。
人手不足で納期も短い世の中とあっては、プラクティスは気にしつつも、
結果が出るほうを優先するのが良いと思いますが、どうですかね?
開発に銀の弾丸は無い
よく銀の弾丸は無いと言われます。
そうであれば、
ベストプラクティスも存在しないのでは?
と思います。
現時点で書いたコードは、一年後にエンジニアとしてレベルが上がっているのであれば、レガシーコードに映るはずです。
どうせレガシーになるなら、現時点でチームが納得のいく、ベストを尽くすまでなのかなぁと思います。
(駄目だった場合の責任取るのもチームですし。)
完全に余談ですが、コードレビューの時に自分のコードが指摘されると、腹が立つという人が居るみたいです。
チームのレギュレーションに沿って、機械的にレビューするなら、そんなことにはならないと思うのですが、どうなんですかね?
Javaとかのtry-catch機構に近い
悪手も状況が違えば、善手になりうると思います。
Javaのtry catchは、強制されるのがイケてないだけで、
コードがシンプルになるという意味では、エラー機構として優れていると思います。
そして、それはGolangでも出来ます。
Webサーバのような、DBなどの外部APIの待ち時間が支配的な状況下においては、実行速度の低下も気にもなりませんし。
まとめ
韓国のエンジニアに見せてもらったGolangの本では、堂々と使おうと書いてあるみたいです。
日本の記事を見てみると書いている人がいないので、書いてみました。
(もしかしたら、日本の場合、強烈なマサカリが飛んでくるので怖くて書けないだけかもしれませんが。。)
システム開発は、トレードオフの連続で、記述的に正しいよりは、結果が出るのかどうか?を軸に考えたほうが良いと思います。
2人しかいないチームで実際にpanic使って書いていますが、かなり良くなったと思っています。
もしかしたら後で後悔することになるかもしれないですが、今の所、その兆候は全く無いですし、そんときはそんときで書き直せば良いのだと思います。
ちゃんとテスト書いているから、なんとかなります。
最後に、英語の記事ですが、下記はサンプルコードもありますので、時間があるときにでも見てみてください。
- 投稿日:2019-09-15T14:24:37+09:00
Ebitenの個人的まとめ
Ebiten
EbitenとはGo言語のシンプルな2Dゲームライブラリです。
自動テクスチャアトラス化、自動バッチ処理などで大量のスプライトを高速に描画できます。
WindowsではCgoが不要。テスト環境
Go 1.12.5
Ebiten 1.9.3
Windows10ウィンドウ
ウィンドウを表示させる
main.gopackage main import ( "log" "github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten/ebitenutil" ) func update(screen *ebiten.Image) error { if ebiten.IsDrawingSkipped() { return nil } ebitenutil.DebugPrint(screen, "Hello, World!") return nil } func main() { if err := ebiten.Run(update, 320, 240, 2, "Hello, World!"); err != nil { log.Fatal(err) } }func ebiten.Run
コールバック関数とウィンドウの幅、高さ、スケーリング、タイトルを設定している。
1秒間にあらかじめ設定されている回数update
関数が呼び出されることになる。ebiten.Run(update, 320, 240, 2, "Hello, World!")第1引数のupdate関数はレシーバでもいい。
type Game struct { hoge Hoge } func (g *Game) update(screen *ebiten.Image) error { if ebiten.IsDrawingSkipped() { return nil } return nil } var game *Game func main() { if err := ebiten.Run(game.update, 320, 240, 2, "Game"); err != nil { log.Fatal(err) } }func ebiten.IsDrawingSkipped
この関数より上に更新の度にしたい計算等の処理を書き。この関数より下に描画関係の処理を書きます。
if ebiten.IsDrawingSkipped() { return nil }更新処理が間に合わなくなると、描画をスキップして次回の更新に間に合わせようとする為にあるようです。
func ebitenutil.DebugPrint
デバッグ用テキスト表示。途中で改行もできる。日本語不可。
TPSとFPS
現在のTPSとFPSを表示させる。
func update(screen *ebiten.Image) error { if ebiten.IsDrawingSkipped() { return nil } msg := fmt.Sprintf("TPS = %0.2f\nFPS = %0.2f", ebiten.CurrentTPS(), ebiten.CurrentFPS()) ebitenutil.DebugPrint(screen, msg) return nil }func ebiten.CurrentTPS
現在1秒間に
update
関数が呼ばれる回数を取得する。
TPS (ticks per second)func ebiten.CurrentFPS
現在1秒間に描画されている回数を取得する。
FPS (frames per second)例えば小さい画像を数万個描画すると、TPS=60, FPS=30という表示になり、計算2回に描画1回という感じで描画回数が少なくなります。
この描画スキップにより安定して
update
関数が呼び出されることで、固定フレームレートを前提とした計算をしても、ほぼほぼ問題は起こらないように思います。ウィンドウアイコン
ウィンドウのアイコンを変更する。ウィンドウのアイコンはWindowsにしかない。Alt+Tabのウィンドウ切り替え時の表示や、タスクバーのアイコンも一緒に変更される。
main.gopackage main import ( "image" _ "image/png" "log" "os" "github.com/hajimehoshi/ebiten" ) var icon image.Image func init() { fp, err := os.Open("ebiten.png") if err != nil { log.Fatal(err) } defer fp.Close() icon, _, err = image.Decode(fp) if err != nil { log.Fatal(err) } } func update(screen *ebiten.Image) error { if ebiten.IsDrawingSkipped() { return nil } return nil } func main() { ebiten.SetWindowIcon([]image.Image{icon}) if err := ebiten.Run(update, 320, 240, 2, "Window Icon"); err != nil { log.Fatal(err) } }画像の読み込み
pngファイルを読み込むので、インポートに
_ "image/png"
を記述する。import ( "image" _ "image/png" "log" "os" "github.com/hajimehoshi/ebiten" )
os.Open()
からのimage.Decode()
でimage.Image
構造体を取得。
ここまではEbitenに限らずgoで画像ファイルを扱う時と同じ。func ebiten.SetWindowIcon
引数には
image.Image
構造体のスライスを渡す。ebiten.Image
ではないことに気を付ける。
update
内でも使用できるが1度実行すればいいようなのでmain
関数内で実行している。
画像のサイズは16x16, 32x32, 64x64であることが望ましい。ウィンドウ非アクティブ時の動作
デフォルトではウィンドウが非アクティブになるとプログラムが一時停止する。
動作を停止させないebiten.SetRunnableInBackground(true)引数に
true
を指定すると非アクティブ状態でも動作し続ける。デフォルトはfalse
。
1度実行するだけでいい。
ブラウザでは意味ない。マウスカーソルを非表示に
1度実行するだけでいい。
カーソル非表示ebiten.SetCursorVisible(false)グラフィック
座標の原点は左上で、右下方向に数字が大きくなる。
画像の描画
main.gopackage main import ( "image" "image/color" _ "image/png" "log" "os" "github.com/hajimehoshi/ebiten" ) var ( uma *ebiten.Image ) func init() { fp, err := os.Open("uma.png") if err != nil { log.Fatal(err) } img, _, err := image.Decode(fp) if err != nil { log.Fatal(err) } uma, err = ebiten.NewImageFromImage(img, ebiten.FilterDefault) if err != nil { log.Fatal(err) } } func update(screen *ebiten.Image) error { if ebiten.IsDrawingSkipped() { return nil } screen.Fill(color.RGBA{0x37, 0x94, 0x6E, 0xFF}) op := &ebiten.DrawImageOptions{} op.GeoM.Translate(64, 64) screen.DrawImage(uma, op) return nil } func main() { if err := ebiten.Run(update, 320, 240, 2, "Image"); err != nil { log.Fatal(err) } }func ebiten.NewImageFromImage
画像から
*ebiten.Image
を作成する。
引数にimage.Image
構造体とフィルタータイプを指定する。func Fill
画像を指定した色で塗りつぶす関数。今回は画面全体を塗りつぶしている。
image/color
を使う。単色のテクスチャを用意したいときにも使える。
img, _ := ebiten.NewImage(16, 16, ebiten.FilterDefault) img.Fill(color.White)フェードインアウト表現にも使える。
type ebiten.DrawImageOptions
描画の際の座標変換や色変換、描画モード等を設定するもの。
op := &ebiten.DrawImageOptions{} op.GeoM.Translate(64, 64) screen.DrawImage(uma, op)
op.GeoM.Translate(64, 64)
とはxとyを64移動させているということ。
ほかにScale
、Rotate
もある。詳しくはGoDocを見る。func DrawImage
画像に対してほかの画像を描画する関数。
今回はスクリーン画像に対してuma
を描画している。ポリゴン
3角形を描画する。自由な変形が可能。
main.gopackage main import ( "image" "image/color" _ "image/png" "log" "os" "github.com/hajimehoshi/ebiten" ) var ( uma *ebiten.Image vertecies []ebiten.Vertex indices []uint16 ) func init() { fp, err := os.Open("uma.png") if err != nil { log.Fatal(err) } img, _, err := image.Decode(fp) if err != nil { log.Fatal(err) } uma, err = ebiten.NewImageFromImage(img, ebiten.FilterDefault) if err != nil { log.Fatal(err) } } func init() { const ( x = 160 y = 64 w = 32 h = 32 ) vertecies = []ebiten.Vertex{ { DstX: x - 16, DstY: y, SrcX: 0, SrcY: 0, ColorR: 1, ColorG: 1, ColorB: 1, ColorA: 1, }, { DstX: x + w - 16, DstY: y, SrcX: 0 + w, SrcY: 0, ColorR: 1, ColorG: 1, ColorB: 1, ColorA: 1, }, { DstX: x + w + 16, DstY: y + h, SrcX: 0 + w, SrcY: 0 + h, ColorR: 1, ColorG: 1, ColorB: 1, ColorA: 1, }, { DstX: x + 16, DstY: y + h, SrcX: 0, SrcY: 0 + h, ColorR: 1, ColorG: 1, ColorB: 1, ColorA: 1, }, } indices = []uint16{0, 1, 2, 0, 2, 3} } func update(screen *ebiten.Image) error { if ebiten.IsDrawingSkipped() { return nil } screen.Fill(color.RGBA{0x37, 0x94, 0x6E, 0xFF}) op := &ebiten.DrawImageOptions{} op.GeoM.Translate(64, 64) screen.DrawImage(uma, op) screen.DrawTriangles(vertecies, indices, uma, nil) return nil } func main() { if err := ebiten.Run(update, 320, 240, 2, "Polygon"); err != nil { log.Fatal(err) } }
右側がポリゴンで変形させて描画したもの。
type ebiten.Vertex
頂点座標、テクスチャ座標(画像の座標であってuvではない)、頂点カラー(maxが1)を設定する構造体。
これのスライスを使用する。func DrawTriangles
画像に対してポリゴンを描画する。
引数に、頂点スライス、インデックススライス、テクスチャ、最後にtype DrawTrianglesOptions
なんだが、今回は不要なのでnil
を渡している。公式サンプル
examples/shapes
では、DrawTriangles
を使用してラインや矩形を描画している。ラインの場合は線の太さを求める処理が必要なようだ。アニメーション
キャラクターアニメーション。
main.gopackage main import ( "fmt" "image" "image/color" _ "image/png" "log" "os" "github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten/ebitenutil" ) var ( umas *ebiten.Image count int frame int ) const duration = 10 func init() { fp, err := os.Open("uma_sheet.png") if err != nil { log.Fatal(err) } img, _, err := image.Decode(fp) if err != nil { log.Fatal(err) } umas, err = ebiten.NewImageFromImage(img, ebiten.FilterDefault) if err != nil { log.Fatal(err) } } func update(screen *ebiten.Image) error { count++ if count > duration { count = 0 frame++ if frame >= 5 { frame = 0 } } if ebiten.IsDrawingSkipped() { return nil } screen.Fill(color.RGBA{0x37, 0x94, 0x6E, 0xFF}) x := frame * 32 uma := umas.SubImage(image.Rect(x, 0, x+32, 32)).(*ebiten.Image) op := &ebiten.DrawImageOptions{} op.GeoM.Translate(64, 64) screen.DrawImage(uma, op) msg := fmt.Sprintf("frame = %d", frame) ebitenutil.DebugPrint(screen, msg) return nil } func main() { if err := ebiten.Run(update, 320, 240, 2, "Animation"); err != nil { log.Fatal(err) } }
カウンタ計算は
ebiten.IsDrawingSkipped()
より上に書く。func SubImage
画像から
image.Rectangle
構造体の範囲の画像を取得する。
ピクセルをコピーではなく共有している。uma := umas.SubImage(image.Rect(x, 0, x+32, 32)).(*ebiten.Image)上記は
SubImage
の戻り値を*ebiten.Image
に変換している。
型アサーション。タイリング
SubImage
を使ったタイルマップ表現。main.gopackage main import ( "image" _ "image/png" "log" "os" "github.com/hajimehoshi/ebiten" ) var ( tiles *ebiten.Image ) var tilemap = [][]int{ {0, 0, 0, 0, 3, 0, 0, 2, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0}, {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 1, 0}, {0, 0, 2, 0, 0, 1, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0}, {0, 0, 0, 1, 0, 0, 4, 0, 0, 0, 0, 4, 0, 5, 0, 0, 1, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 2, 3, 0, 0, 2}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 1, 0, 0, 2, 0, 0, 0, 0, 0, 0, 2, 0, 1, 0, 0, 0, 1, 2, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 2, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 5, 0}, {0, 4, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 1, 0, 4, 0, 3, 0, 0, 0, 2, 0, 0, 0, 0, 4, 2, 0}, {0, 0, 0, 0, 0, 0, 2, 0, 1, 0, 0, 0, 0, 0, 5, 0, 1, 0, 0, 0}, {1, 0, 0, 2, 0, 0, 0, 0, 0, 0, 2, 0, 0, 1, 0, 0, 0, 0, 0, 0}, {0, 2, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0}, } func init() { fp, err := os.Open("grass_tiles.png") if err != nil { log.Fatal(err) } img, _, err := image.Decode(fp) if err != nil { log.Fatal(err) } tiles, err = ebiten.NewImageFromImage(img, ebiten.FilterDefault) if err != nil { log.Fatal(err) } } func update(screen *ebiten.Image) error { if ebiten.IsDrawingSkipped() { return nil } for col := 0; col < 15; col++ { for row := 0; row < 20; row++ { n := tilemap[col][row] x := (n % 4) * 16 y := n / 4 * 16 tile := tiles.SubImage(image.Rect(x, y, x+16, y+16)).(*ebiten.Image) op := &ebiten.DrawImageOptions{} op.GeoM.Translate(float64(row*16), float64(col*16)) screen.DrawImage(tile, op) } } return nil } func main() { if err := ebiten.Run(update, 320, 240, 2, "Tilemap"); err != nil { log.Fatal(err) } }
Geomety Matrix
行列を使った図形の変換。
移動してから回転することと、回転してから移動する事では結果が違うことに気を付ける。
順番が大事。
主に使うのは以下の3つだと思う。他はGoDoc見る。func (g *GeoM) Rotate(theta float64) func (g *GeoM) Scale(x, y float64) func (g *GeoM) Translate(tx, ty float64)Rotate
引数にはRadianを指定する。
ラジアンの求め方r := float64(角度) * math.Pi / 180 op.GeoM.Rotate(r)Scale
xとy軸で拡大縮小。
画像を左右反転させる場合にもこれを使う。
左右反転op.GeoM.Scale(-1, 1)反転時の中心は画像の原点なので、あらかじめ
Translate
で移動させるか、反転後に移動させるかすること。Color Matrix
行列を使った色の変換。
公式サンプルexamples/flood
,examples/hsv
,examples/hue
あたりを見る。描画する画像を塗りつぶして点滅させる。
main.gopackage main import ( "image" "image/color" _ "image/png" "log" "os" "github.com/hajimehoshi/ebiten" ) var ( uma *ebiten.Image count int flush bool ) func init() { fp, err := os.Open("uma.png") if err != nil { log.Fatal(err) } img, _, err := image.Decode(fp) if err != nil { log.Fatal(err) } uma, err = ebiten.NewImageFromImage(img, ebiten.FilterDefault) if err != nil { log.Fatal(err) } } func update(screen *ebiten.Image) error { count++ if count > 10 { count = 0 flush = !flush } if ebiten.IsDrawingSkipped() { return nil } screen.Fill(color.RGBA{0x37, 0x94, 0x6E, 0xFF}) op := &ebiten.DrawImageOptions{} op.GeoM.Translate(64, 64) if flush { op.ColorM.Scale(0, 0, 0, 1) op.ColorM.Translate(1, 1, 0.8, 0) } screen.DrawImage(uma, op) return nil } func main() { if err := ebiten.Run(update, 320, 240, 2, "Flush"); err != nil { log.Fatal(err) } }
透明度以外を操作
op.ColorM.Scale(0, 0, 0, 1) op.ColorM.Translate(1, 1, 0.8, 0)
Scale
でアルファ以外の色要素を0にしてから、Translate
で塗りつぶしている。
Translate
で指定する値は0~1。半透明に描画したいとき
透明度を半分にop.ColorM.Scale(1, 1, 1, 0.5)描画モード
CompositeMode
描画方法を設定する。デフォルトはアルファブレンディング。
使い方はexample/additive
,example/masking
を見る。加算モードop = &ebiten.DrawImageOptions{} op.CompositeMode = ebiten.CompositeModeLighter screen.DrawImage(img, op)キーボード入力
キーが押されているか、押した瞬間、離した瞬間、押している長さを調べることができる。
サンプルではスペースキーの状態を調べている。
main.gopackage main import ( "fmt" "log" "github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten/ebitenutil" "github.com/hajimehoshi/ebiten/inpututil" ) func update(screen *ebiten.Image) error { pressed := ebiten.IsKeyPressed(ebiten.KeySpace) justpressed := inpututil.IsKeyJustPressed(ebiten.KeySpace) justreleased := inpututil.IsKeyJustReleased(ebiten.KeySpace) duration := inpututil.KeyPressDuration(ebiten.KeySpace) if ebiten.IsDrawingSkipped() { return nil } if pressed { ebitenutil.DebugPrintAt(screen, "Space key pressed.", 0, 0) } if justpressed { ebitenutil.DebugPrintAt(screen, "Space key just pressed.", 0, 16) } if justreleased { ebitenutil.DebugPrintAt(screen, "Space key just pressed.", 0, 32) } msg := fmt.Sprintf("Space key duration = %d", duration) ebitenutil.DebugPrintAt(screen, msg, 0, 48) return nil } func main() { if err := ebiten.Run(update, 320, 240, 2, "Keyboard"); err != nil { log.Fatal(err) } }キーの状態判定は
ebiten.IsDrawingSkipped()
より上に書くこと。func ebiten.IsKeyPressed
引数に指定したキーが押されている状態かを返す。
引数にebiten.Key
型の定数を指定する。戻り値はbool
。
定義されているキーはGoDocを見る。
ebiten.Key
型は元はint
なので型キャストできる。k := ebiten.Key(0)func inpututil.IsKeyJustPressed
キーが押された瞬間かどうかを返す。
func inpututil.IsKeyJustReleased
キーが離された瞬間かどうかを返す。
func inpututil.KeyPressDuration
キーの押されている時間を返す。戻り値は
int
。マウス入力
マウスカーソル座標、マウスホイールの移動量、各種ボタンの状態を調べることができる。
サンプルではカーソル座標、ホイール、マウス左ボタンの状態を見ている。
main.gopackage main import ( "fmt" "log" "github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten/ebitenutil" ) var wheelx, wheely float64 func update(screen *ebiten.Image) error { x, y := ebiten.CursorPosition() xoff, yoff := ebiten.Wheel() wheelx += xoff wheely += yoff leftpressed := ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) if ebiten.IsDrawingSkipped() { return nil } msg := fmt.Sprintf("Cursor x = %d, y = %d\nWheel x = %0.2f, y = %0.2f", x, y, wheelx, wheely) ebitenutil.DebugPrint(screen, msg) if leftpressed { ebitenutil.DebugPrintAt(screen, "Left mousebutton pressed.", 0, 32) } return nil } func main() { if err := ebiten.Run(update, 320, 240, 2, "Mouse"); err != nil { log.Fatal(err) } }func ebiten.CursorPosition
戻り値にマウスカーソル座標x,yが返る。
この座標にはウィンドウのスケールもかかる。func ebiten.Wheel
戻り値にマウスホイールの移動した値が返る。
func ebiten.IsMouseButtonPressed
マウスのボタンが押されているか調べる。
引数には定数を指定する。MouseButtonLeft
,MouseButtonRight
,MouseButtonMiddle
のいずれか。func inpututil.IsMouseButtonJustPressed
押された瞬間かどうか。
func inpututil.IsMouseButtonJustReleased
離した瞬間かどうか。
func inpututil.MouseButtonPressDuration
押し続けている時間を取得。
ゲームパッド
テキスト
用意したフォントを使ってテキストを描画する。
日本語可。main.gopackage main import ( "image/color" "io/ioutil" "log" "github.com/golang/freetype/truetype" "golang.org/x/image/font" "github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten/text" ) var ( mplusfont font.Face pixelfont font.Face ) func init() { data, err := ioutil.ReadFile("mplus-1p-regular.ttf") if err != nil { log.Fatal(err) } ttf, err := truetype.Parse(data) if err != nil { log.Fatal(err) } op := truetype.Options{Size: 24, DPI: 72, Hinting: font.HintingFull} mplusfont = truetype.NewFace(ttf, &op) } func init() { data, err := ioutil.ReadFile("PixelMplus12-Regular.ttf") if err != nil { log.Fatal(err) } ttf, err := truetype.Parse(data) if err != nil { log.Fatal(err) } op := truetype.Options{Size: 12, DPI: 72, Hinting: font.HintingNone, SubPixelsX: 16} pixelfont = truetype.NewFace(ttf, &op) } func update(screen *ebiten.Image) error { if ebiten.IsDrawingSkipped() { return nil } screen.Fill(color.RGBA{0x37, 0x94, 0x6E, 0xff}) text.Draw(screen, "はじめてのEbiten。", mplusfont, 32, 32, color.White) text.Draw(screen, "はじめてのEbiten。", pixelfont, 32, 128, color.White) return nil } func main() { if err := ebiten.Run(update, 320, 240, 2, "Text"); err != nil { log.Fatal(err) } }
下は等倍表示。
こちらのフォントを使用させてもらいました。
M+FONTS
PixelMplusフォント読み込み
インポートを追加import ( "github.com/golang/freetype/truetype" "golang.org/x/image/font" )ttfファイルからbyteデータを取得data, err := ioutil.ReadFile("mplus-1p-regular.ttf")Fontに展開ttf, err := truetype.Parse(data)オプションを設定してfont.Faceを作成op := truetype.Options{Size: 24, DPI: 72, Hinting: font.HintingFull} mplusfont = truetype.NewFace(ttf, &op)truetype.Options構造体
Size: 0なら12が使われる DPI: 0なら72が使われる Hinting: 0なら無し GlyphCacheEntries: 0なら512 SubPixelsX: 0なら4が使われる SubPixelsY: 0なら1が使われる詳細は
freetype/truetype/face.go
を見る。テキスト描画
指定する座標はテキストの左下。
func text.Draw
text.Draw(screen, "はじめてのEbiten。", mplusfont, 32, 32, color.White) text.Draw(screen, "はじめてのEbiten。", pixelfont, 32, 128, color.White)文字の縁取り
1ドットずつ上下左右にずらして、重ねて描画すると縁取りしたように見せられる。
同じ文字を複数回描画することは、コストの高い処理ではない。
下は等倍表示。
文字の幅を調べる
文章の折り返し位置を調べるために。
1文字ずつ文字の幅を調べていく。
advance, ok := mplusfont.GlyphAdvance(rune('あ')) fmt.Println(advance.Floor())戻り値が固定小数点なので、整数に変換する。
オーディオ
.wav
,.ogg
,.mp3
形式の音声データを再生する。基本的な形
main.gopackage main import ( "log" "os" "github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten/audio" "github.com/hajimehoshi/ebiten/audio/vorbis" "github.com/hajimehoshi/ebiten/ebitenutil" ) const ( sampleRate = 44100 ) var ( audioContext *audio.Context ) func init() { var err error audioContext, err = audio.NewContext(sampleRate) if err != nil { log.Fatal(err) } f, err := os.Open("audio_xxx.ogg") if err != nil { log.Fatal(err) } s, err := vorbis.Decode(audioContext, f) if err != nil { log.Fatal(err) } p, err := audio.NewPlayer(audioContext, s) if err != nil { log.Fatal(err) } p.Play() } func update(screen *ebiten.Image) error { if ebiten.IsDrawingSkipped() { return nil } if !audioContext.IsReady() { ebitenutil.DebugPrint(screen, "audio context not ready") } return nil } func main() { if err := ebiten.Run(update, 320, 240, 2, "Audio play"); err != nil { log.Fatal(err) } }func audio.NewContext
オーディオコンテキストを作成する。
指定するサンプルレートは44100または48000がよい。Windows10ではプレイヤーを再生するまで、
IsReady()
がfalse
を返す。audioContext.IsReady()func vorbis.Decode
oggから再生可能なストリームソースに変換する。
2ch16bit指定したサンプルレートに変換されるので、再生するファイルのサンプルレート等が違っても問題ない。
os.Close()
でファイルを閉じてはいけない。GCに回収されるときに閉じられる。func audio.NewPlayer
ストリームソースからプレイヤーを作成。
Play()
で再生する。他にPause()
,SetVolume()
などがある。
1つのソースを複数のプレイヤーで共有できない。ループ再生
通常との違う部分だけ抜粋。
s, err := vorbis.Decode(audioContext, f) l := audio.NewInfiniteLoop(s, s.Length()) p, err := audio.NewPlayer(audioContext, l)func audio.NewInfiniteLoop
ソースから無限ループストリームを作成。
長さはバイト数を指定している。もう一つイントロ部分を設定できる
NewInfiniteLoopWithIntro
というのもある。同じソースを重複再生
短い効果音を重複して再生したいときは、
NewPlayerFromBytes
でプレイヤーを作成する。スペースキーを連打すると、音が重なっているのがわかると思う。
main.gopackage main import ( "io/ioutil" "log" "os" "github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten/audio" "github.com/hajimehoshi/ebiten/audio/wav" "github.com/hajimehoshi/ebiten/ebitenutil" "github.com/hajimehoshi/ebiten/inpututil" ) const ( sampleRate = 44100 ) var ( audioContext *audio.Context byteSource []byte ) func init() { var err error audioContext, err = audio.NewContext(sampleRate) if err != nil { log.Fatal(err) } f, err := os.Open("jab.wav") if err != nil { log.Fatal(err) } s, err := wav.Decode(audioContext, f) if err != nil { log.Fatal(err) } byteSource, err = ioutil.ReadAll(s) if err != nil { log.Fatal(err) } } func update(screen *ebiten.Image) error { if inpututil.IsKeyJustPressed(ebiten.KeySpace) { p, err := audio.NewPlayerFromBytes(audioContext, byteSource) if err != nil { return err } p.SetVolume(0.5) p.Play() } if ebiten.IsDrawingSkipped() { return nil } if !audioContext.IsReady() { ebitenutil.DebugPrint(screen, "audio context not ready") } return nil } func main() { if err := ebiten.Run(update, 320, 240, 2, "Audio byte source"); err != nil { log.Fatal(err) } }func audio.NewPlayerFromBytes
バイトソースからプレイヤーを作成する。
同じバイトソースを複数のプレイヤーで共有できる。examples
audio, audioinfiniteloop, pcm, piano, sinewave, wav
を見る。リソースファイルの埋め込み
Ebitenのexamplesでは画像や音声ファイルを
.go
ファイルに埋め込んでいるようです。埋め込み方法
file2byteslice
こちらのプログラムを使います。
go get
してからgo install
するとbinに配置してくれるので楽です。
使い方はexamples/resources/generate.go
を見ると、go generate
を使ってまとめて処理しているようです。画像
埋め込んだbyteスライスから
ebiten.Image
を作成。変更部分だけ抜粋import "my/resources/images" img, _, err := image.Decode(bytes.NewReader(images.Hoge_png)) hogeImage, _ = ebiten.NewImageFromImage(img, ebiten.FilterDefault)変換した
.go
ファイルのパッケージをインポートする。
Hoge_png(グローバル変数)というbyteスライスをリーダーに変換してからデコードする。フォント
もともとbyteスライスをパースしているのであまり違いが無い。
変更部分だけ抜粋import "my/resources/fonts" ttf, err := truetype.Parse(fonts.Hoge_ttf)オーディオ
byteスライスを
audio.BytesReadSeekCloser
に渡してからデコードする。変更部分だけ抜粋import raudio "my/resources/audio" s, err := wav.Decode(audioContext, audio.BytesReadSeekCloser(raudio.Hoge_wav))ウェブブラウザー
GopherJS
WebAssembly
その他
スクリーンショット
撮影するシュートカットキーを設定する。
一時的に環境変数を設定する。
コマンドプロンプトだと1行で書けない。エスケープキーで撮影set EBITEN_SCREENSHOT_KEY=escape go run main.goメインのフォルダに保存される。windowスケール1倍で保存される。
os.Setenv
関数で設定しても大丈夫だった。一時的な環境変数func init() { err := os.Setenv("EBITEN_SCREENSHOT_KEY", "escape") if err != nil { log.Fatal(err) } }内部イメージをダンプ
環境変数
EBITEN_INTERNAL_IMAGES_KEY
にキーを設定すると、内部イメージをすべてダンプすることができる。
これはビルドタグにebitendebug
を設定している場合にのみ有効。コマンドプロンプトset EBITEN_INTERNAL_IMAGES_KEY=escape go run -tags=ebitendebug main.goコマンドプロンプトを非表示に
Windowsで起動時に一瞬だけ表示されるのを出てこないようにする。
go build -ldflags -H=windowsguiEbitenリンク
- 投稿日:2019-09-15T00:19:02+09:00
【Go】GitHub Markdown APIを叩いてみる
はじめに
GitHub Markdown APIの使い方に関しては、以下の記事を参照。
サンプルコード
package main import ( "fmt" "io/ioutil" "net/http" "strings" ) func main() { apiURL := "https://api.github.com/markdown/raw" markdown := "# Hello World" // リクエストを生成 req, err := http.NewRequest("POST", apiURL, strings.NewReader(markdown)) if err != nil { panic(err) } req.Header.Add("Content-Type", "text/plain") // POSTリクエストを送信 client := &http.Client{} res, err := client.Do(req) if err != nil { panic(err) } defer res.Body.Close() // レスポンスを読み取り bytes, err := ioutil.ReadAll(res.Body) if err != nil { panic(err) } fmt.Println(string(bytes)) // => <h1> // <a id="user-content-hello-world" class="anchor" href="#hello-world" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Hello World</h1> }参考
- 投稿日:2019-09-15T00:16:55+09:00
怖くないよね?マルチステージビルド
会社の本番環境をAWSのEKSに移行することになりその際にgolangのアプリを配布する際に使用したマルチステージビルドについて備忘録を交えながら記事を書いていきます。
先に言うんですけど
すみません僕Docker自体初めてです
今まで特に自分でDockerなどで環境構築をしたことがなかったのでこれが初めてのDocker作成です。
ほとんど分かっていないのでこいつダメやなって思いながら見てくださいマルチステージビルドについて
https://github.com/moby/moby/pull/31257簡単に言うと、fromでから作られた2つの複数のFROMイメージ間のファイルを COPY --from で直接を参照できるようになったのと AS で中間イメージに名前が付けられるようになったと言うものです。
golangってクロスコンパイルなどでシングルバイナリさえあればgolangの実行環境不要ですもんね
(golangにいては割愛します。)早速やりましょうか
基本的なファイル構成はこちら
├build(ここにbuildの成果物が格納される) │ └ appname(実行バイナリ) ├cmd(実行ディレクトリ) │ └ main.go ├configs(設定ファイル関連) ├bitbucket-pipelines.yml ├.env(環境変数設定ファイル) ├docker-compose.yml ├Dockerfile ├go.mod ├go.sum ├Makefile(タスクランナー実行ファイル)Makefile
基本的には今回は
go build
なども含めて全てMakefile側に任せています。makefileに関しての設定はここで書きませんがちゃんとbuild配下にバイナリが入るように以下のように設定しました
build: @go build -ldflags="-w -s" -o build/appname ./cmd/main.goこれで
make build
のコマンドをターミナルで実行するとbuild配下にappnameと言うシングルバイナリが作成されますDockerfile
FROM golang:1.12.9 as builder ADD . /appname/ WORKDIR /appname/ ENV GO111MODULE=on COPY go.mod . COPY go.sum . RUN go mod download COPY . . RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 make build FROM alpine RUN apk update \ && apk add --no-cache COPY --from=builder /appname/build /app/build ENTRYPOINT [ "/app/build/appname" ]まずポイントを解説すると
FROM golang:1.12.9 as builderこれでgolangのbaseimageを作成しますこの
as builder
と言うのがこの後にこのimageを渡すためのエイリアスになりますENV GO111MODULE=on COPY go.mod . COPY go.sum . RUN go mod download COPY . .go.modはgolangのモジュール関連の設定ファイルです(jsのpackage.jsonみたいなもの)
https://qiita.com/propella/items/e49bccc88f3cc2407745
ENV GO111MODULE=on
はおまじないみたいなもので現行のgo1.13の環境では必要ないと思う(動かなかったらいれてください)RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 make buildここでgoのbuildを行います
make build
コマンドは先ほど作成したMakefile
のトリガーとしてbuild
を設定していたのでこのようなコマンドでタスクの実行が可能ですクロスコンパイルについてはこちらを参照してください
https://qiita.com/xshirade/items/abeb0d595be27cb6326eFROM alpineこちらはDockerの公式が提供している軽量なLinuxのイメージです。
https://alpinelinux.org/COPY --from=builder /appname/build /app/buildここで先ほどbuildした実行バイナリの格納ファイル等をこちらにCOPYしています
これでマルチステージビルドの完成です補足
これで
ducker build
などでもいいのですが開発環境等でmysqlに接続できる等などの確認が必要だと思うのでdokcer-compose.ymlで環境の構築を行いますdocker-compose.ymlversion: "3" services: mysql: image: mysql:5.7.26 container_name: db env_file: - .env ports: - "3306:3306" volumes: - db_data:/var/lib/mysql - ./mysql/scripts/init:/docker-entrypoint-initdb.d - ./mysql/config:/etc/mysql/conf.d api: build: . depends_on: - mysql env_file: - .env ports: - "8080:8080" volumes: db_data:これで
$ docker-composer build && docker-compose up -dこちらで接続ができているかなどの確認(go側にmysqlに接続などのプログラムは書いてくださいね)
最後に
僕自身初めてのDockerだったので探り探りでの実装だったのですがもし他にこれの方がいいよってものがありましたら是非教えて頂けますと幸いです。
最後まで読んで頂いてありがとうございました。次にbitbucket-pipelines.ymlを使用したECRへのPUSHをしたいと思います。