タッチ操作で複数のウィンドウから1つ選ぶ
今回は2つの取組みを紹介します٩( 'ω' )و
- 枠が点滅するウィンドウの実装
- 複数あるウィンドウをタッチ操作で選択して、選択したウィンドウだけ枠を点滅させる
解説に使うコードは以下に置いています。
kemokemo/ebiten-sketchbook - blink-framewindow
kemokemo/ebiten-sketchbook - select-one
使用環境
- go version go1.10.2 windows/amd64
- ebiten rev. c39c211d1fe0744b3a2315a122f89c7074a54a49
- gomobile version +bf2d30a Sun Apr 29 17:04:17 2018
- Android NDK r16b
実装内容の説明
点滅機能付きのウィンドウFrameWindow
の説明
枠付きのウィンドウ
実はとても単純な仕組みです。下図をご覧ください。
- 枠の内側となる画像
innerImg
- 上記よりも上下左右に枠の幅
frameWidth
の分だけ大きい画像frameImg
これらの画像を作って、innerImg
を上に重ねて描画するだけです。
// NewFrameWindow returns a FrameWindow.
//
// The width and height are used for the inner region excluding the frame.
// If 0 is set to the frameWidth, the frame will not be drawn.
func NewFrameWindow(x, y, width, height, frameWidth int) (*FrameWindow, error) {
fw := FrameWindow{
rect: image.Rectangle{
Min: image.Point{X: x, Y: y},
Max: image.Point{X: x + width, Y: y + height},
},
}
var err error
fw.innerImg, err = ebiten.NewImage(width, height, ebiten.FilterDefault)
if err != nil {
return nil, err
}
err = fw.innerImg.Fill(color.White)
if err != nil {
return nil, err
}
fw.innerOp = &ebiten.DrawImageOptions{}
fw.innerOp.GeoM.Translate(float64(x), float64(y))
if frameWidth > 0 {
fw.frameImg, err = ebiten.NewImage(width+frameWidth*2, height+frameWidth*2, ebiten.FilterDefault)
if err != nil {
return nil, err
}
err = fw.frameImg.Fill(color.White)
if err != nil {
return nil, err
}
fw.frameDarkOp = &ebiten.DrawImageOptions{}
fw.frameDarkOp.GeoM.Translate(float64(x-frameWidth), float64(y-frameWidth))
fw.frameLightOp = &ebiten.DrawImageOptions{}
fw.frameLightOp.GeoM.Translate(float64(x-frameWidth), float64(y-frameWidth))
}
return &fw, nil
}
枠を点滅させる
ebiten
で画像を描画する時にはDrawImageOptions
を指定しますが、枠の描画に使うDrawImageOptions
に明るいframeLightOp
と暗いframeDarkOp
を用意しておいて、frameImg
の描画に使うDrawImageOptions
を時間経過で変更すれば点滅動作となります。やったね!٩( 'ω' )و
FrameWindow
の使い方
動作イメージ
実際に使ってみるとこんな感じになります。
インスタンスの生成と色や点滅の設定
var err error
frameWindow, err = ui.NewFrameWindow(20, 10, 100, 120, 5)
if err != nil {
return err
}
frameWindow.SetColors(
color.RGBA{64, 64, 64, 255},
color.RGBA{192, 192, 192, 255},
color.RGBA{0, 148, 255, 255})
frameWindow.SetBlink(true)
SetColors
では引数で、ウィンドウ内側
、枠の暗い時の色
、枠の明るい時の色
を順に指定します。
// SetColors sets the colors of the window's inner region and the frame's
// normal color.
// If you need to blink the frame, please use the SetBlinkFrame method.
func (w *FrameWindow) SetColors(inner, frameDark, frameLight color.RGBA) {
w.innerOp.ColorM.Scale(colorScale(inner))
以下省略
SetBlink
にtrue
を渡せば点滅On
、点滅させないときはfalse
を渡します。
描画する
あとは、ebiten.Run
に第一引数で渡すコールバック関数、例えば以下のようなupdate
メソッドなど定期的に描画の更新を行う処理でFrameWindow
のDrawWindow
メソッドを実行するだけです。
func update(screen *ebiten.Image) error {
if ebiten.IsRunningSlowly() {
return nil
}
frameWindow.DrawWindow(screen)
return nil
}
タッチ位置とウィンドウの領域の重なりを検知する
ここまでに説明してきたFrameWindow
は、複数表示してキャラクター選択画面
やアイテム選択画面
などで使うことをイメージしています。これをタッチ操作で選択したいと思います。
前回の記事の試作でタッチ位置は取得可能になっていますので、後は「タッチした位置とウィンドウ描画領域の重なりの有無」が分かればタッチされたウィンドウが判別できそうです。
実は上記で説明したNewFrameWindow
メソッドの冒頭で、枠を含まないウィンドウ領域を算出しておりGetWindowRect
メソッドで取得できる仕組みにしてあります。
// GetWindowRect returns the rectangle of this window.
func (w *FrameWindow) GetWindowRect() image.Rectangle {
return w.rect
}
そこで、ユーザーが最初にタッチした位置
が引数で渡した領域に含まれるか否か
をbool
値で返してくれるメソッドを作りました。FrameWindow
のGetWindowRect
から取得したimage.Rectangle
を渡せば、タッチされたウィンドウが判別できます。
// IsTouched returns the state that touched or not.
func IsTouched(r image.Rectangle) bool {
IDs := ebiten.TouchIDs()
if len(IDs) == 0 {
return false
}
// use only the first touched point
sort.Slice(IDs, func(i, j int) bool {
return IDs[i] < IDs[j]
})
x, y := ebiten.TouchPosition(IDs[0])
min := r.Min
max := r.Max
if min.X < x && x < max.X && min.Y < y && y < max.Y {
return true
}
return false
}
タッチされたウィンドウだけ点滅させる
後は「タッチされたよ!」とわかったウィンドウに対してSetBlink(true)
を実行して描画すればおkです。
次回はスワイプ操作のハンドリングとかやってみたい!