はじめに
ゲームを作るなら、場面(シーン)切り替えは必須ですよね。
街のシーンからフィールドのシーンへ、そして戦闘のシーンへ変わったり…
ebitenにはUpdateとDrawというシーンでの処理や描画を行う関数が用意されていますが、これをシーンごとに管理する機能はないので、管理できる機能を作成します。
ファイル構造
┣go.mod
┣go.sum
┣main.go
┗game
┣ game.go
┣ firstScene.go
┣ secondScene.go
┗ thirdScene.go
一番上のmain.goがすべての始まりになります。
game.goの中では、シーンに合わせたゲーム構造体をmain.goに返して、main.goではそれを実行しています。
それでは中を見ていきます。
package main
import (
"github.com/hajimehoshi/ebiten/v2"
"github.com/youraccount/yourproject/game"
)
func main() {
game, err := game.NewGame() // game.goからGame構造体を取得
if err != nil {
panic(err)
}
ebiten.SetWindowSize(320, 240)
if err := ebiten.RunGame(game); err != nil {
panic(err)
}
}
package game
import (
"github.com/hajimehoshi/ebiten/v2"
)
var (
Displayed_scene Scene = &FirstScene{} // 各シーンを持ち回る変数
Global_valiable int = 0
)
type Game struct {
scene Scene
}
func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
return 320, 240
}
// Game構造体のUpdate シーン毎に設定されているUpdateを実行する
func (g *Game) Update() error {
// 実行するシーンを変数より取得
g.scene = Displayed_scene
g.scene.Update(g)
return nil
}
// Game構造体のDraw関数 シーンごとに設定されているDraw関数を実行する
func (g *Game) Draw(screen *ebiten.Image) {
g.scene.Draw(screen, g)
}
// main.goから呼び出されている関数
// このファイルで宣言されているGame構造体を返す
func NewGame() (*Game, error) {
game := &Game{}
return game, nil
}
// 各シーンは必ず持たないと行けない関数をインターフェースとして定義
type Scene interface {
Update(g *Game)
Draw(screen *ebiten.Image, g *Game)
}
package game
import (
"fmt"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
"github.com/hajimehoshi/ebiten/v2/inpututil"
)
type FirstScene struct {
}
// firstSceneで実行するUpdateの中身
func (f *FirstScene) Update(game *Game) {
// エンターが押されたらgame.goで持っているシーン管理用変数に
// SeconSceneをセットする
if inpututil.IsKeyJustPressed(ebiten.KeyEnter) {
Displayed_scene = &SecondScene{}
}
Global_valiable += 1
}
// firstSceneで実行するDrawの中身
func (f *FirstScene) Draw(screen *ebiten.Image, game *Game) {
// game.goで設定した変数を表示する
ebitenutil.DebugPrint(screen, fmt.Sprintf("first screen %v", Global_valiable))
}
package game
import (
"fmt"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
"github.com/hajimehoshi/ebiten/v2/inpututil"
)
type SecondScene struct {
}
// SecondSceneで実行するUpdateの中身
func (s *SecondScene) Update(game *Game) {
// エンター押したらthirdSceneを表示させる
if inpututil.IsKeyJustPressed(ebiten.KeyEnter) {
Displayed_scene = &ThirdScene{}
}
}
func (s *SecondScene) Draw(screen *ebiten.Image, game *Game) {
ebitenutil.DebugPrint(screen, fmt.Sprintf("second screen %v", Global_valiable))
}
package game
import (
"fmt"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
"github.com/hajimehoshi/ebiten/v2/inpututil"
)
// first sceneを定義
type ThirdScene struct {
}
// thirdSceneで実行するUpdateの中身
func (t *ThirdScene) Update(game *Game) {
// エンターが押されたらFirstSceneに切り替える
if inpututil.IsKeyJustPressed(ebiten.KeyEnter) {
Displayed_scene = &FirstScene{}
}
}
func (t *ThirdScene) Draw(screen *ebiten.Image, game *Game) {
ebitenutil.DebugPrint(screen, fmt.Sprintf("third screen %v", Global_valiable))
}
ざっくり解説
基本的には、公式のツアーでやっていることと同じです。
異なる点としては、UpdateとDrawの中身がシーンごとに切り替わるようにしている点です。
グローバル変数として持っているDisplayed_scene
にそれぞれのシーンのSceneインターフェースが入っており、これらをGame構造体のsceneに入れてあげて、g.scene.Update(g)とするとそれぞれのシーンのUpdateが実行される仕組みです。
また、これを実行するとグローバル変数としてもっているGlobal_valiable
がfirstSceneのときにしか加算されないのが見てわかります。
こうすることでシーンをまたいだ変数管理もできそうですね。
課題
今はgame.goと同じディレクトリにすべてのシーンを置かないといけないため、シーンが多くなると管理が大変になるのでできれば別ディレクトリに分けたいのですがやり方がうまくわからず。(ディレクトリを切るところまでは良いが、例えば街とフィールドのシーンをお互いに切り替えたいときにpackageのインポートの循環が発生してしまう。)
現在方法を模索中です。
シーンマネージャー的なのを考えないといけないかも?