diff --git a/examples/splashmenu/main.go b/examples/splashmenu/main.go index 476ce16..318a61b 100644 --- a/examples/splashmenu/main.go +++ b/examples/splashmenu/main.go @@ -11,13 +11,16 @@ import ( ) func main() { + + //setup manager manager := groovy.NewManager() - - loadScenes(&manager) - + manager.SetDimensions(groovy.Area{Width: 1280, Height: 720}) ebiten.SetWindowSize(manager.Info.Dimension.Width, manager.Info.Dimension.Height) ebiten.SetWindowTitle(manager.Info.Name) + loadScenes(&manager) + + //identification fmt.Println(manager.Info.Name + ": v" + manager.Info.Version) if err := ebiten.RunGame(&manager); err != nil { @@ -28,14 +31,31 @@ func main() { // Example loading of two scenes func loadScenes(m *groovy.Manager) { + //call the loaders for each scene + loadSplash(m) + loadMenu(m) + + //reset the manager to start scene 1 + m.ResetScenes() +} + +// creates splash screen, sets completion handler +func loadSplash(m *groovy.Manager) { + //create splash screen and append as first scene in manager sceneSplash := splashmenu.NewSplash() m.AddScene(&sceneSplash) - - sceneMenu := splashmenu.NewMenu() - m.AddScene(&sceneMenu) - - //sets current scene to the splash menu - m.SetCurrentScene(0) - - m.SetDimensions(groovy.Area{Width: 1280, Height: 720}) +} + +// creates menu screen, populates the options, sets key event handler +func loadMenu(m *groovy.Manager) { + //create menu with defined options, append to manager + sceneMenu := splashmenu.NewMenu() + sceneMenu.SetOptions(map[int]splashmenu.MenuOption{ + 1: {Description: "splash", SelectionEvent: groovy.RESET, Mapping: ebiten.Key1}, + 2: {Description: "menu"}, + 3: {Description: "swing"}, + 4: {Description: "exit", SelectionEvent: groovy.ENDGAME, Mapping: ebiten.Key4}, + }) + + m.AddScene(&sceneMenu) } diff --git a/examples/splashmenu/scenes/menu.go b/examples/splashmenu/scenes/menu.go index 5448c5a..9572592 100644 --- a/examples/splashmenu/scenes/menu.go +++ b/examples/splashmenu/scenes/menu.go @@ -1,8 +1,10 @@ package splashmenu import ( + "cosmos/diego/groovy" "image/color" "log" + "strconv" "github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2/examples/resources/fonts" @@ -13,7 +15,8 @@ import ( ) var ( - menuFont font.Face + titleFont font.Face + menuFont font.Face ) func init() { @@ -23,8 +26,17 @@ func init() { } const dpi = 72 + titleFont, err = opentype.NewFace(tt, &opentype.FaceOptions{ + Size: 16, + DPI: dpi, + Hinting: font.HintingVertical, + }) + if err != nil { + log.Fatal(err) + } + menuFont, err = opentype.NewFace(tt, &opentype.FaceOptions{ - Size: 12, + Size: 10, DPI: dpi, Hinting: font.HintingVertical, }) @@ -34,35 +46,62 @@ func init() { } type Menu struct { - bgcolor color.RGBA - completed bool + bgcolor color.RGBA + options map[int]MenuOption + events map[groovy.SceneEvent]func() } func NewMenu() Menu { return Menu{ - bgcolor: color.RGBA{0x33, 0x33, 0x99, 0xFF}, - completed: false, + bgcolor: color.RGBA{0x33, 0x33, 0x99, 0xFF}, + events: make(map[groovy.SceneEvent]func()), } } func (m *Menu) Draw(screen *ebiten.Image) { screen.Fill(m.bgcolor) - text.Draw(screen, "menu", menuFont, 40, 40, color.White) + text.Draw(screen, "menu", titleFont, 40, 40, color.White) + + m.RenderOptions(screen) +} + +func (m Menu) SetEventHandler(event groovy.SceneEvent, f func()) { + m.events[event] = f +} + +func (m *Menu) SetOptions(options map[int]MenuOption) { + m.options = make(map[int]MenuOption) + + for k, v := range options { + m.options[k] = v + } +} + +func (m *Menu) RenderOptions(screen *ebiten.Image) { + + var offset int = 20 + + for k, v := range m.options { + m.options[k] = v + text.Draw(screen, strconv.Itoa(k)+": "+v.Description, menuFont, 40, 60+offset*k, color.White) + } + } func (m *Menu) Update() error { var keysPressed []ebiten.Key keysPressed = inpututil.AppendPressedKeys(keysPressed[:0]) - for _, k := range keysPressed { - if k == ebiten.KeyEnter { - m.completed = true + + for _, o := range m.options { + if m.events[o.SelectionEvent] != nil { + for _, k := range keysPressed { + if k == o.Mapping { + m.events[o.SelectionEvent]() + } + } } } return nil } - -func (m *Menu) Completed() bool { - return m.completed -} diff --git a/examples/splashmenu/scenes/menuoption.go b/examples/splashmenu/scenes/menuoption.go new file mode 100644 index 0000000..a91697d --- /dev/null +++ b/examples/splashmenu/scenes/menuoption.go @@ -0,0 +1,13 @@ +package splashmenu + +import ( + "cosmos/diego/groovy" + + "github.com/hajimehoshi/ebiten/v2" +) + +type MenuOption struct { + Description string + SelectionEvent groovy.SceneEvent + Mapping ebiten.Key +} diff --git a/examples/splashmenu/scenes/splash.go b/examples/splashmenu/scenes/splash.go index aae41a8..e324313 100644 --- a/examples/splashmenu/scenes/splash.go +++ b/examples/splashmenu/scenes/splash.go @@ -1,6 +1,7 @@ package splashmenu import ( + "cosmos/diego/groovy" "image/color" "log" @@ -34,14 +35,19 @@ func init() { type Splash struct { bgcolor color.RGBA - completed bool increment int + events map[groovy.SceneEvent]func() +} + +// GetSceneEvents implements groovy.Scene. +func (*Splash) GetSceneEvents() []groovy.SceneEvent { + return nil } func NewSplash() Splash { return Splash{ - bgcolor: color.RGBA{0xFF, 0xFF, 0xFF, 0xFF}, - completed: false, + bgcolor: color.RGBA{0xFF, 0xFF, 0xFF, 0xFF}, + events: make(map[groovy.SceneEvent]func()), } } @@ -58,12 +64,12 @@ func (s *Splash) Update() error { s.bgcolor.B = (s.bgcolor.B - 2) % 0xFF if s.bgcolor.R == 0x00 { - s.completed = true + s.events[groovy.COMPLETED]() } return nil } -func (s *Splash) Completed() bool { - return s.completed +func (s Splash) SetEventHandler(event groovy.SceneEvent, f func()) { + s.events[event] = f } diff --git a/manager.go b/manager.go index 084b3fa..1c722a8 100644 --- a/manager.go +++ b/manager.go @@ -25,8 +25,10 @@ type GameInfo struct { type Manager struct { Info GameInfo currentScene Scene - currentSceneId int + currentSceneId uint + nextSceneId uint scenes []Scene + EventMap map[SceneEvent]func() } // can be used to create default manager instance @@ -37,9 +39,11 @@ func NewManager() Manager { Version: "1.0", Dimension: Area{ Width: defaultWidth, - Height: defaultHeight}, + Height: defaultHeight, + }, }, currentSceneId: 0, + nextSceneId: 1, } } @@ -50,29 +54,13 @@ func (m *Manager) Update() error { return nil } - m.CheckTransitions() - m.CheckExit() - //call the current scene's update method return m.currentScene.Update() } -// check for exit condition -func (m *Manager) CheckExit() { - if m.currentSceneId >= len(m.scenes) { - os.Exit(0) - } -} - -// check for scene completion and if reached, set up next scene (if available) -func (m *Manager) CheckTransitions() { - if m.currentScene.Completed() { - m.currentSceneId++ - if m.currentSceneId < len(m.scenes) { - m.currentScene = m.scenes[m.currentSceneId] - } - } +func (m *Manager) Quit() { + os.Exit(0) } // calls current scene's draw method if the currentscene is valid @@ -89,18 +77,57 @@ func (m *Manager) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHe // appends scene to the managed scenes func (m *Manager) AddScene(s Scene) { + setDefaultHandlers(m, s) m.scenes = append(m.scenes, s) } -// sets the current scene, based on sceneindex -func (m *Manager) SetCurrentScene(sceneId int) { - if sceneId >= 0 && sceneId < len(m.scenes) { - m.currentSceneId = sceneId - m.currentScene = m.scenes[m.currentSceneId] - } +// sets the default callback handlers for a given scene within manager +// Default Handling behaviours: +// +// reset: sets (scene, nextscene) to {0, 1} +// scene completion: sets (scene, nextscene) to {nextscene, nextscene+1} +// end game: shutdown groovy +// +// note: NOOP and RELOAD are purposefully not mapped; they are scene +// specific and should be mapped to by user of groovy +func setDefaultHandlers(m *Manager, s Scene) { + s.SetEventHandler(RESET, func() { m.ResetScenes() }) + s.SetEventHandler(COMPLETED, func() { m.TransitionScene() }) + s.SetEventHandler(ENDGAME, func() { m.Quit() }) } -// check for exit condition +// we're going to reset the scene to the first one +func (m *Manager) ResetScenes() { + m.currentSceneId = 0 + m.nextSceneId = 1 + m.SetCurrentScene(0) +} + +// sets the current scene, based on sceneindex n +// n > scenelist, quit +// otherwise, scene = n +func (m *Manager) SetCurrentScene(sceneId uint) { + if sceneId >= uint(len(m.scenes)) { + m.Quit() + } + m.currentSceneId = sceneId + m.currentScene = m.scenes[sceneId] +} + +func (m *Manager) TransitionScene() { + m.SetCurrentScene(m.nextSceneId) +} + +func (m *Manager) SetNextScene(sceneId uint) { + m.nextSceneId = sceneId +} + +// sets sene dimensions func (m *Manager) SetDimensions(a Area) { m.Info.Dimension = a } + +// report number of total scenes +func (m *Manager) SceneCount() uint { + return uint(len(m.scenes)) +} diff --git a/scene.go b/scene.go index 1044db7..bda5d1d 100644 --- a/scene.go +++ b/scene.go @@ -2,8 +2,23 @@ package groovy import "github.com/hajimehoshi/ebiten/v2" +type SceneEvent int64 + +const ( + NOOP SceneEvent = 0 + RESET SceneEvent = 1 // reset to initial scene + RELOAD SceneEvent = 3 // reload current scene + COMPLETED SceneEvent = 4 // current scene has completed + ENDGAME SceneEvent = 5 // shutdown all scenes +) + type Scene interface { Update() error Draw(screen *ebiten.Image) - Completed() bool + SetEventHandler(e SceneEvent, f func()) +} + +type ArgoScale interface { + Draw(screen *ebiten.Image) + GetSceneEvents() []SceneEvent }