タイトル画面を表示する
タイトル画面を表示する
「title.go」ファイルを新規作成します。次のサンプルコードをコーディングして実行するとタイトル画面(図2)が出て、画面をクリックすると画面が真っ白になります。クリック判定にはタイトル画像の下にボタンウィジェットを重ねてボタンを押します。
・サンプルコード「mainloop.go」ファイル
package main
import (
"time"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
)
func (g *Game) SetState(s GameState) {
g.state = s
// 全消去(背景だけ残す)
g.root.Objects = []fyne.CanvasObject{g.bg}
switch s {
case StateTitle:
ShowTitle(g)
}
canvas.Refresh(g.root)
}
func (g *Game) Update() {
}
func (g *Game) Start() {
ticker := time.NewTicker(time.Second / fps)
go func() {
for range ticker.C {
fyne.Do(func() {
g.Update()
canvas.Refresh(g.root)
})
}
}()
}【サンプルコードの解説】
「canvas」モジュールをインポートして、画像を描けるキャンバスを使えるようにします。
「SetState」メソッドで背景以外を全て消し、ゲーム状態がStateTitleならShowTitle関数を呼び出します。
キャンバスをリフレッシュします。
「Update」メソッドは空で宣言します。
「Start」メソッドで「ticker」変数に1フレームごとの経過時間を取得します。
「for range」で1フレームの時間が経過するごとにUpdateメソッドを呼び出してリフレッシュします。
・サンプルコード「title.go」ファイル
package main
import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/widget"
)
func ShowTitle(g *Game) {
img := canvas.NewImageFromFile("images/Title.png")
img.FillMode = canvas.ImageFillOriginal
size := img.MinSize()
img.Resize(size)
// 中央に配置
pos := fyne.NewPos((width-size.Width)/2, (height-size.Height)/2)
img.Move(pos)
btn := widget.NewButton("", func() {
g.SetState(StatePlaying)
})
btn.Importance = widget.LowImportance
btn.Resize(size)
btn.Move(pos)
g.root.Add(btn)
g.root.Add(img)
}【サンプルコードの解説】
「widget」モジュールをインポートしてUIパーツを読み込めるようにします。
"images/Title.png"ファイルを読み込み、元の大きさで画面中央に配置します。
タイトル画像の下にボタンを配置してクリック判定し、クリックされたらゲーム状態を「StatePlaying」にします。ボタンの背景色を透過しタイトル画像を同じ大きさと位置にします。
ルートにボタンとタイトル画像を追加します。
色ブロックを処理する
次に、メインループだけ書き換えて色ブロック(図3)を表示し、クリックしたり画面外に出たりしたら消す処理をします。クリック判定はタイトル画面のクリック判定と同じく、ボタンを色ブロックに重ねます。色ブロックは「sprites」配列で管理して、配列に追加したり削除したりします。
・サンプルコード「mainloop.go」ファイル
package main
import (
"image/color"
"math/rand"
"slices"
"strconv"
"time"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/widget"
)
func (g *Game) SetState(s GameState) {
g.state = s
// 全消去(背景だけ残す)
g.root.Objects = []fyne.CanvasObject{g.bg}
switch s {
case StateTitle:
ShowTitle(g)
case StatePlaying:
g.startGame()
}
canvas.Refresh(g.root)
}
func (g *Game) startGame() {
g.sprites = nil
g.bg.FillColor = color.White
g.bg.Refresh()
for i := 0; i < g.stage*3; i++ {
g.spawn(i % 3)
}
}
func (g *Game) spawn(i int) {
path := "images/" + strconv.Itoa(i) + ".png"
img := canvas.NewImageFromFile(path)
img.FillMode = canvas.ImageFillOriginal
size := img.MinSize()
img.Resize(size)
x := rand.Float32() * (width - size.Width)
y := rand.Float32() * (height - size.Height)
sprite := &Sprite{
x: x,
y: y,
vx: rand.Float32()*2 - 1,
vy: rand.Float32()*2 - 1,
}
btn := widget.NewButton("", func() {
g.removeSprite(sprite)
})
btn.Resize(size)
obj := container.NewStack(btn, img)
obj.Resize(size)
obj.Move(fyne.NewPos(x, y))
sprite.obj = obj
g.root.Add(obj)
g.sprites = append(g.sprites, sprite)
}
func (g *Game) removeSprite(target *Sprite) {
for i, s := range g.sprites {
if s == target {
g.root.Remove(s.obj)
g.sprites = append(g.sprites[:i], g.sprites[i+1:]...)
break
}
}
}
func (g *Game) Update() {
if g.state != StatePlaying {
return
}
for i := 0; i < len(g.sprites); i++ {
s := g.sprites[i]
s.x += s.vx
s.y += s.vy
s.obj.Move(fyne.NewPos(s.x, s.y))
size := s.obj.Size()
if s.x < -size.Width || s.x > width || s.y < -size.Height || s.y > height {
g.removeSprite(s)
break
}
}
}
func (g *Game) Start() {
(中略)
}【サンプルコードの解説】
importでモジュールを読み込みます。
SetStateメソッドでゲーム状態が「StatePlaying」になったら「startGame」メソッドでゲームを開始します。
「startGame」メソッドで3色の色ブロックをスポーン(発生)させます。
「spawn」メソッドでi番のpngファイルを読み込み、元画像の大きさでランダムな位置に配置します。それをクリック判定するボタンも同じ大きさで同じ位置に追加します。色ブロックとボタンをsprites配列に追加します。
「removeSprite」メソッドでターゲットのスプライトをsprites配列から消します。
「Update」メソッドで「StatePlaying」ゲーム状態以外ならメソッドを抜けます。
スプライトを全て平行移動し、画面外に出たらsprites配列から取り除きます。
見る人が見れば分かるのに評価されていない作品もたくさんあると思います。筆者が作った「共通点を探せ!」はドコモ「MEDIAS Wアプリ開発コンテスト」でグランプリをとったのに、Google Playでは2026年4月現在たった500ダウンロードしかありません…。ひろゆきが「クリエイターが1番身の程を知る」と言っていました…。悔しいです…。
