From f485f803c8a14473223997f21b025d79ee3b8afe Mon Sep 17 00:00:00 2001 From: Tim Sarbin Date: Fri, 25 Oct 2019 20:28:14 -0400 Subject: [PATCH] More layout cleanup --- Common/SceneInterface.go | 13 --- Engine.go | 114 ++++++++------------ Scenes/Scene.go | 13 +++ SceneMainMenu.go => Scenes/SceneMainMenu.go | 82 +++++++------- Sound/AudioProvider.go | 52 +++++++++ UI/Manager.go | 31 +++++- 6 files changed, 177 insertions(+), 128 deletions(-) delete mode 100644 Common/SceneInterface.go create mode 100644 Scenes/Scene.go rename SceneMainMenu.go => Scenes/SceneMainMenu.go (61%) create mode 100644 Sound/AudioProvider.go diff --git a/Common/SceneInterface.go b/Common/SceneInterface.go deleted file mode 100644 index 9a939654..00000000 --- a/Common/SceneInterface.go +++ /dev/null @@ -1,13 +0,0 @@ -package Common - -import ( - "github.com/hajimehoshi/ebiten" -) - -// SceneInterface defines the function necessary for scene management -type SceneInterface interface { - Load() - Unload() - Render(screen *ebiten.Image) - Update() -} diff --git a/Engine.go b/Engine.go index 8486212f..37e352d7 100644 --- a/Engine.go +++ b/Engine.go @@ -4,19 +4,21 @@ import ( "encoding/json" "io/ioutil" "log" + "math" "path" "strings" "sync" + "github.com/essial/OpenDiablo2/Sound" + "github.com/essial/OpenDiablo2/Common" "github.com/essial/OpenDiablo2/MPQ" "github.com/essial/OpenDiablo2/Palettes" "github.com/essial/OpenDiablo2/ResourcePaths" + "github.com/essial/OpenDiablo2/Scenes" "github.com/essial/OpenDiablo2/UI" "github.com/hajimehoshi/ebiten" - "github.com/hajimehoshi/ebiten/audio" - "github.com/hajimehoshi/ebiten/audio/wav" ) // EngineConfig defines the configuration for the engine, loaded from config.json @@ -30,16 +32,6 @@ type EngineConfig struct { MpqLoadOrder []string } -// CursorButton represents a mouse button -type CursorButton uint8 - -const ( - // CursorButtonLeft represents the left mouse button - CursorButtonLeft CursorButton = 1 - // CursorButtonRight represents the right mouse button - CursorButtonRight CursorButton = 2 -) - // Engine is the core OpenDiablo2 engine type Engine struct { Settings EngineConfig // Engine configuration settings from json file @@ -47,39 +39,31 @@ type Engine struct { Palettes map[Palettes.Palette]Common.Palette // Color palettes SoundEntries map[string]SoundEntry // Sound configurations LoadingSprite *Common.Sprite // The sprite shown when loading stuff - CursorX int // X position of the cursor - CursorY int // Y position of the cursor - CursorButtons CursorButton // The buttons that are currently being pressed - LoadingProgress float64 // LoadingProcess is a range between 0.0 and 1.0. If set, loading screen displays. - CurrentScene Common.SceneInterface // The current scene being rendered + loadingProgress float64 // LoadingProcess is a range between 0.0 and 1.0. If set, loading screen displays. + stepLoadingSize float64 // The size for each loading step + CurrentScene Scenes.Scene // The current scene being rendered UIManager *UI.Manager // The UI manager - nextScene Common.SceneInterface // The next scene to be loaded at the end of the game loop - audioContext *audio.Context // The Audio context - bgmAudio *audio.Player // The audio player + SoundManager *Sound.Manager // The sound manager + nextScene Scenes.Scene // The next scene to be loaded at the end of the game loop fullscreenKey bool // When true, the fullscreen toggle is still being pressed } // CreateEngine creates and instance of the OpenDiablo2 engine func CreateEngine() *Engine { result := &Engine{ - LoadingProgress: float64(0.0), - CurrentScene: nil, - nextScene: nil, + CurrentScene: nil, + nextScene: nil, } result.loadConfigurationFile() result.mapMpqFiles() result.loadPalettes() result.loadSoundEntries() + result.SoundManager = Sound.CreateManager(result) result.UIManager = UI.CreateManager(result) - audioContext, err := audio.NewContext(22050) - if err != nil { - log.Fatal(err) - } - result.audioContext = audioContext result.LoadingSprite = result.LoadSprite(ResourcePaths.LoadingScreen, Palettes.Loading) loadingSpriteSizeX, loadingSpriteSizeY := result.LoadingSprite.GetSize() result.LoadingSprite.MoveTo(int(400-(loadingSpriteSizeX/2)), int(300+(loadingSpriteSizeY/2))) - result.SetNextScene(CreateMainMenu(result)) + result.SetNextScene(Scenes.CreateMainMenu(result, result.UIManager, result.SoundManager)) return result } @@ -142,7 +126,7 @@ func (v *Engine) LoadFile(fileName string) []byte { // IsLoading returns true if the engine is currently in a loading state func (v *Engine) IsLoading() bool { - return v.LoadingProgress < 1.0 + return v.loadingProgress < 1.0 } func (v *Engine) loadPalettes() { @@ -190,12 +174,16 @@ func (v *Engine) updateScene() { v.CurrentScene = v.nextScene v.nextScene = nil v.UIManager.Reset() - v.CurrentScene.Load() -} - -// CursorButtonPressed determines if the specified button has been pressed -func (v *Engine) CursorButtonPressed(button CursorButton) bool { - return v.CursorButtons&button > 0 + thingsToLoad := v.CurrentScene.Load() + v.SetLoadingStepSize(1.0 / float64(len(thingsToLoad))) + v.ResetLoading() + go func() { + for _, f := range thingsToLoad { + f() + v.StepLoading() + } + v.FinishLoading() + }() } // Update updates the internal state of the engine @@ -217,22 +205,15 @@ func (v *Engine) Update() { if v.IsLoading() { return } - v.CursorButtons = 0 - if ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) { - v.CursorButtons |= CursorButtonLeft - } - if ebiten.IsMouseButtonPressed(ebiten.MouseButtonRight) { - v.CursorButtons |= CursorButtonRight - } + v.CurrentScene.Update() v.UIManager.Update() } // Draw draws the game func (v *Engine) Draw(screen *ebiten.Image) { - v.CursorX, v.CursorY = ebiten.CursorPosition() - if v.LoadingProgress < 1.0 { - v.LoadingSprite.Frame = uint8(Common.Max(0, Common.Min(uint32(len(v.LoadingSprite.Frames)-1), uint32(float64(len(v.LoadingSprite.Frames)-1)*v.LoadingProgress)))) + if v.loadingProgress < 1.0 { + v.LoadingSprite.Frame = uint8(Common.Max(0, Common.Min(uint32(len(v.LoadingSprite.Frames)-1), uint32(float64(len(v.LoadingSprite.Frames)-1)*v.loadingProgress)))) v.LoadingSprite.Draw(screen) } else { if v.CurrentScene == nil { @@ -244,29 +225,26 @@ func (v *Engine) Draw(screen *ebiten.Image) { } // SetNextScene tells the engine what scene to load on the next update cycle -func (v *Engine) SetNextScene(nextScene Common.SceneInterface) { +func (v *Engine) SetNextScene(nextScene Scenes.Scene) { v.nextScene = nextScene } -// PlayBGM plays an infinitely looping background track -func (v *Engine) PlayBGM(song string) { - go func() { - if v.bgmAudio != nil { - v.bgmAudio.Close() - } - audioData := v.LoadFile(song) - d, err := wav.Decode(v.audioContext, audio.BytesReadSeekCloser(audioData)) - if err != nil { - log.Fatal(err) - } - s := audio.NewInfiniteLoop(d, int64(len(audioData))) - - v.bgmAudio, err = audio.NewPlayer(v.audioContext, s) - if err != nil { - log.Fatal(err) - } - // Play the infinite-length stream. This never ends. - v.bgmAudio.Rewind() - v.bgmAudio.Play() - }() +// SetLoadingStepSize sets the size of the loading step +func (v *Engine) SetLoadingStepSize(size float64) { + v.stepLoadingSize = size +} + +// ResetLoading resets the loading progress +func (v *Engine) ResetLoading() { + v.loadingProgress = 0.0 +} + +// StepLoading increments the loading progress +func (v *Engine) StepLoading() { + v.loadingProgress = math.Min(1.0, v.loadingProgress+v.stepLoadingSize) +} + +// FinishLoading terminates the loading phase +func (v *Engine) FinishLoading() { + v.loadingProgress = 1.0 } diff --git a/Scenes/Scene.go b/Scenes/Scene.go new file mode 100644 index 00000000..e33b30fd --- /dev/null +++ b/Scenes/Scene.go @@ -0,0 +1,13 @@ +package Scenes + +import ( + "github.com/hajimehoshi/ebiten" +) + +// Scene defines the function necessary for scene management +type Scene interface { + Load() []func() + Unload() + Render(screen *ebiten.Image) + Update() +} diff --git a/SceneMainMenu.go b/Scenes/SceneMainMenu.go similarity index 61% rename from SceneMainMenu.go rename to Scenes/SceneMainMenu.go index a840784d..6656aaee 100644 --- a/SceneMainMenu.go +++ b/Scenes/SceneMainMenu.go @@ -1,10 +1,11 @@ -package OpenDiablo2 +package Scenes import ( "image/color" "github.com/essial/OpenDiablo2/Common" "github.com/essial/OpenDiablo2/Palettes" + "github.com/essial/OpenDiablo2/Sound" "github.com/essial/OpenDiablo2/UI" "github.com/essial/OpenDiablo2/ResourcePaths" @@ -13,7 +14,9 @@ import ( // MainMenu represents the main menu type MainMenu struct { - engine *Engine + uiManager *UI.Manager + soundManager *Sound.Manager + fileProvider Common.FileProvider trademarkBackground *Common.Sprite background *Common.Sprite diabloLogoLeft *Common.Sprite @@ -27,72 +30,63 @@ type MainMenu struct { } // CreateMainMenu creates an instance of MainMenu -func CreateMainMenu(engine *Engine) *MainMenu { +func CreateMainMenu(fileProvider Common.FileProvider, uiManager *UI.Manager, soundManager *Sound.Manager) *MainMenu { result := &MainMenu{ - engine: engine, + fileProvider: fileProvider, + uiManager: uiManager, + soundManager: soundManager, showTrademarkScreen: true, } - return result } // Load is called to load the resources for the main menu -func (v *MainMenu) Load() { - v.engine.PlayBGM(ResourcePaths.BGMTitle) - go func() { - loadStep := 1.0 / 8.0 - v.engine.LoadingProgress = 0 - { - v.copyrightLabel = UI.CreateLabel(v.engine, ResourcePaths.FontFormal12, Palettes.Static) +func (v *MainMenu) Load() []func() { + v.soundManager.PlayBGM(ResourcePaths.BGMTitle) + return []func(){ + func() { + v.copyrightLabel = UI.CreateLabel(v.fileProvider, ResourcePaths.FontFormal12, Palettes.Static) v.copyrightLabel.Alignment = UI.LabelAlignCenter v.copyrightLabel.SetText("Diablo 2 is © Copyright 2000-2016 Blizzard Entertainment") v.copyrightLabel.ColorMod = color.RGBA{188, 168, 140, 255} v.copyrightLabel.MoveTo(400, 500) - v.engine.LoadingProgress += loadStep - } - { - v.copyrightLabel2 = UI.CreateLabel(v.engine, ResourcePaths.FontFormal12, Palettes.Static) + }, + func() { + v.copyrightLabel2 = UI.CreateLabel(v.fileProvider, ResourcePaths.FontFormal12, Palettes.Static) v.copyrightLabel2.Alignment = UI.LabelAlignCenter v.copyrightLabel2.SetText("All Rights Reserved.") v.copyrightLabel2.ColorMod = color.RGBA{188, 168, 140, 255} v.copyrightLabel2.MoveTo(400, 525) - v.engine.LoadingProgress += loadStep - } - { - v.background = v.engine.LoadSprite(ResourcePaths.GameSelectScreen, Palettes.Sky) + }, + func() { + v.background = v.fileProvider.LoadSprite(ResourcePaths.GameSelectScreen, Palettes.Sky) v.background.MoveTo(0, 0) - v.engine.LoadingProgress += loadStep - } - { - v.trademarkBackground = v.engine.LoadSprite(ResourcePaths.TrademarkScreen, Palettes.Sky) + }, + func() { + v.trademarkBackground = v.fileProvider.LoadSprite(ResourcePaths.TrademarkScreen, Palettes.Sky) v.trademarkBackground.MoveTo(0, 0) - v.engine.LoadingProgress += loadStep - } - { - v.diabloLogoLeft = v.engine.LoadSprite(ResourcePaths.Diablo2LogoFireLeft, Palettes.Units) + }, + func() { + v.diabloLogoLeft = v.fileProvider.LoadSprite(ResourcePaths.Diablo2LogoFireLeft, Palettes.Units) v.diabloLogoLeft.Blend = true v.diabloLogoLeft.Animate = true v.diabloLogoLeft.MoveTo(400, 120) - v.engine.LoadingProgress += loadStep - } - { - v.diabloLogoRight = v.engine.LoadSprite(ResourcePaths.Diablo2LogoFireRight, Palettes.Units) + }, + func() { + v.diabloLogoRight = v.fileProvider.LoadSprite(ResourcePaths.Diablo2LogoFireRight, Palettes.Units) v.diabloLogoRight.Blend = true v.diabloLogoRight.Animate = true v.diabloLogoRight.MoveTo(400, 120) - v.engine.LoadingProgress += loadStep - } - { - v.diabloLogoLeftBack = v.engine.LoadSprite(ResourcePaths.Diablo2LogoBlackLeft, Palettes.Units) + }, + func() { + v.diabloLogoLeftBack = v.fileProvider.LoadSprite(ResourcePaths.Diablo2LogoBlackLeft, Palettes.Units) v.diabloLogoLeftBack.MoveTo(400, 120) - v.engine.LoadingProgress += loadStep - } - { - v.diabloLogoRightBack = v.engine.LoadSprite(ResourcePaths.Diablo2LogoBlackRight, Palettes.Units) + }, + func() { + v.diabloLogoRightBack = v.fileProvider.LoadSprite(ResourcePaths.Diablo2LogoBlackRight, Palettes.Units) v.diabloLogoRightBack.MoveTo(400, 120) - v.engine.LoadingProgress = 1.0 - } - }() + }, + } } // Unload unloads the data for the main menu @@ -123,7 +117,7 @@ func (v *MainMenu) Render(screen *ebiten.Image) { // Update runs the update logic on the main menu func (v *MainMenu) Update() { if v.showTrademarkScreen { - if v.engine.CursorButtonPressed(CursorButtonLeft) { + if v.uiManager.CursorButtonPressed(UI.CursorButtonLeft) { v.leftButtonHeld = true v.showTrademarkScreen = false } diff --git a/Sound/AudioProvider.go b/Sound/AudioProvider.go new file mode 100644 index 00000000..950a74fd --- /dev/null +++ b/Sound/AudioProvider.go @@ -0,0 +1,52 @@ +package Sound + +import ( + "log" + + "github.com/essial/OpenDiablo2/Common" + "github.com/hajimehoshi/ebiten/audio" + "github.com/hajimehoshi/ebiten/audio/wav" +) + +// Manager provides sound +type Manager struct { + fileProvider Common.FileProvider + audioContext *audio.Context // The Audio context + bgmAudio *audio.Player // The audio player +} + +// CreateManager creates a sound provider +func CreateManager(fileProvider Common.FileProvider) *Manager { + result := &Manager{ + fileProvider: fileProvider, + } + audioContext, err := audio.NewContext(22050) + if err != nil { + log.Fatal(err) + } + result.audioContext = audioContext + return result +} + +// PlayBGM plays an infinitely looping background track +func (v *Manager) PlayBGM(song string) { + go func() { + if v.bgmAudio != nil { + v.bgmAudio.Close() + } + audioData := v.fileProvider.LoadFile(song) + d, err := wav.Decode(v.audioContext, audio.BytesReadSeekCloser(audioData)) + if err != nil { + log.Fatal(err) + } + s := audio.NewInfiniteLoop(d, int64(len(audioData))) + + v.bgmAudio, err = audio.NewPlayer(v.audioContext, s) + if err != nil { + log.Fatal(err) + } + // Play the infinite-length stream. This never ends. + v.bgmAudio.Rewind() + v.bgmAudio.Play() + }() +} diff --git a/UI/Manager.go b/UI/Manager.go index b44699f6..fe0ebdf9 100644 --- a/UI/Manager.go +++ b/UI/Manager.go @@ -7,10 +7,23 @@ import ( "github.com/hajimehoshi/ebiten" ) +// CursorButton represents a mouse button +type CursorButton uint8 + +const ( + // CursorButtonLeft represents the left mouse button + CursorButtonLeft CursorButton = 1 + // CursorButtonRight represents the right mouse button + CursorButtonRight CursorButton = 2 +) + // Manager represents the UI manager type Manager struct { - widgets []*Widget - cursorSprite *Common.Sprite + widgets []*Widget + cursorSprite *Common.Sprite + cursorButtons CursorButton + CursorX int + CursorY int } // CreateManager creates a new instance of a UI manager @@ -41,5 +54,17 @@ func (v *Manager) Draw(screen *ebiten.Image) { // Update updates all of the UI elements func (v *Manager) Update() { - + v.cursorButtons = 0 + if ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) { + v.cursorButtons |= CursorButtonLeft + } + if ebiten.IsMouseButtonPressed(ebiten.MouseButtonRight) { + v.cursorButtons |= CursorButtonRight + } + v.CursorX, v.CursorY = ebiten.CursorPosition() +} + +// CursorButtonPressed determines if the specified button has been pressed +func (v *Manager) CursorButtonPressed(button CursorButton) bool { + return v.cursorButtons&button > 0 }