Code cleanup. Added basic UI interface types.

This commit is contained in:
Tim Sarbin 2019-10-25 18:40:27 -04:00
parent ee317dafef
commit d5b37a74a3
10 changed files with 205 additions and 98 deletions

13
Common/Drawable.go Normal file
View File

@ -0,0 +1,13 @@
package Common
import "github.com/hajimehoshi/ebiten"
// Drawable represents an instance that can be drawn
type Drawable interface {
Draw(target *ebiten.Image)
GetSize() (uint32, uint32)
MoveTo(x, y int)
GetLocation() (int, int)
GetVisible() bool
SetVisible(bool)
}

View File

@ -9,6 +9,7 @@ import (
"sync" "sync"
"github.com/essial/OpenDiablo2/Common" "github.com/essial/OpenDiablo2/Common"
"github.com/essial/OpenDiablo2/Palettes"
"github.com/essial/OpenDiablo2/ResourcePaths" "github.com/essial/OpenDiablo2/ResourcePaths"
"github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten"
@ -27,31 +28,34 @@ type EngineConfig struct {
MpqLoadOrder []string MpqLoadOrder []string
} }
// Engine is the core OpenDiablo2 engine // CursorButton represents a mouse button
type CursorButton uint8 type CursorButton uint8
const ( const (
CursorButtonLeft CursorButton = 1 // CursorButtonLeft represents the left mouse button
CursorButtonLeft CursorButton = 1
// CursorButtonRight represents the right mouse button
CursorButtonRight CursorButton = 2 CursorButtonRight CursorButton = 2
) )
// Engine is the core OpenDiablo2 engine
type Engine struct { type Engine struct {
Settings EngineConfig // Engine configuration settings from json file Settings EngineConfig // Engine configuration settings from json file
Files map[string]string // Map that defines which files are in which MPQs Files map[string]string // Map that defines which files are in which MPQs
Palettes map[string]Palette // Color palettes Palettes map[Palettes.Palette]Palette // Color palettes
SoundEntries map[string]SoundEntry // Sound configurations SoundEntries map[string]SoundEntry // Sound configurations
CursorSprite Sprite // The sprite shown for cursors CursorSprite *Sprite // The sprite shown for cursors
LoadingSprite Sprite // The sprite shown when loading stuff LoadingSprite *Sprite // The sprite shown when loading stuff
CursorX int // X position of the cursor CursorX int // X position of the cursor
CursorY int // Y position of the cursor CursorY int // Y position of the cursor
CursorButtons CursorButton // The buttons that are currently being pressed 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. 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 CurrentScene Common.SceneInterface // The current scene being rendered
nextScene Common.SceneInterface // The next scene to be loaded at the end of the game loop nextScene Common.SceneInterface // The next scene to be loaded at the end of the game loop
fontCache map[string]*MPQFont // The font cash fontCache map[string]*MPQFont // The font cash
audioContext *audio.Context // The Audio context audioContext *audio.Context // The Audio context
bgmAudio *audio.Player // The audio player bgmAudio *audio.Player // The audio player
fullscreenKey bool // When true, the fullscreen toggle is still being pressed fullscreenKey bool // When true, the fullscreen toggle is still being pressed
} }
// CreateEngine creates and instance of the OpenDiablo2 engine // CreateEngine creates and instance of the OpenDiablo2 engine
@ -71,8 +75,8 @@ func CreateEngine() *Engine {
log.Fatal(err) log.Fatal(err)
} }
result.audioContext = audioContext result.audioContext = audioContext
result.CursorSprite = result.LoadSprite(ResourcePaths.CursorDefault, result.Palettes["units"]) result.CursorSprite = result.LoadSprite(ResourcePaths.CursorDefault, Palettes.Units)
result.LoadingSprite = result.LoadSprite(ResourcePaths.LoadingScreen, result.Palettes["loading"]) result.LoadingSprite = result.LoadSprite(ResourcePaths.LoadingScreen, Palettes.Loading)
loadingSpriteSizeX, loadingSpriteSizeY := result.LoadingSprite.GetSize() loadingSpriteSizeX, loadingSpriteSizeY := result.LoadingSprite.GetSize()
result.LoadingSprite.MoveTo(int(400-(loadingSpriteSizeX/2)), int(300+(loadingSpriteSizeY/2))) result.LoadingSprite.MoveTo(int(400-(loadingSpriteSizeX/2)), int(300+(loadingSpriteSizeY/2)))
result.SetNextScene(CreateMainMenu(result)) result.SetNextScene(CreateMainMenu(result))
@ -142,14 +146,14 @@ func (v *Engine) IsLoading() bool {
} }
func (v *Engine) loadPalettes() { func (v *Engine) loadPalettes() {
v.Palettes = make(map[string]Palette) v.Palettes = make(map[Palettes.Palette]Palette)
log.Println("loading palettes") log.Println("loading palettes")
for file := range v.Files { for file := range v.Files {
if strings.Index(file, "/data/global/palette/") != 0 || strings.Index(file, ".dat") != len(file)-4 { if strings.Index(file, "/data/global/palette/") != 0 || strings.Index(file, ".dat") != len(file)-4 {
continue continue
} }
nameParts := strings.Split(file, `/`) nameParts := strings.Split(file, `/`)
paletteName := nameParts[len(nameParts)-2] paletteName := Palettes.Palette(nameParts[len(nameParts)-2])
palette := CreatePalette(paletteName, v.GetFile(file)) palette := CreatePalette(paletteName, v.GetFile(file))
v.Palettes[paletteName] = palette v.Palettes[paletteName] = palette
} }
@ -169,9 +173,9 @@ func (v *Engine) loadSoundEntries() {
} }
// LoadSprite loads a sprite from the game's data files // LoadSprite loads a sprite from the game's data files
func (v *Engine) LoadSprite(fileName string, palette Palette) Sprite { func (v *Engine) LoadSprite(fileName string, palette Palettes.Palette) *Sprite {
data := v.GetFile(fileName) data := v.GetFile(fileName)
sprite := CreateSprite(data, palette) sprite := CreateSprite(data, v.Palettes[palette])
return sprite return sprite
} }
@ -241,13 +245,13 @@ func (v *Engine) SetNextScene(nextScene Common.SceneInterface) {
} }
// GetFont creates or loads an existing font // GetFont creates or loads an existing font
func (v *Engine) GetFont(font, palette string) *MPQFont { func (v *Engine) GetFont(font string, palette Palettes.Palette) *MPQFont {
cacheItem, exists := v.fontCache[font+"_"+palette] cacheItem, exists := v.fontCache[font+"_"+string(palette)]
if exists { if exists {
return cacheItem return cacheItem
} }
newFont := CreateMPQFont(v, font, v.Palettes[palette]) newFont := CreateMPQFont(v, font, palette)
v.fontCache[font+"_"+palette] = newFont v.fontCache[font+"_"+string(palette)] = newFont
return newFont return newFont
} }

View File

@ -1,18 +1,22 @@
package OpenDiablo2 package OpenDiablo2
import "github.com/essial/OpenDiablo2/Palettes"
// MPQFontSize represents the size of a character in a font
type MPQFontSize struct { type MPQFontSize struct {
Width uint8 Width uint8
Height uint8 Height uint8
} }
// MPQFont represents a font
type MPQFont struct { type MPQFont struct {
Engine *Engine Engine *Engine
FontSprite Sprite FontSprite *Sprite
Metrics map[uint8]MPQFontSize Metrics map[uint8]MPQFontSize
} }
// CreateMPQFont creates an instance of a MPQ Font // CreateMPQFont creates an instance of a MPQ Font
func CreateMPQFont(engine *Engine, font string, palette Palette) *MPQFont { func CreateMPQFont(engine *Engine, font string, palette Palettes.Palette) *MPQFont {
result := &MPQFont{ result := &MPQFont{
Engine: engine, Engine: engine,
Metrics: make(map[uint8]MPQFontSize), Metrics: make(map[uint8]MPQFontSize),

View File

@ -1,17 +1,20 @@
package OpenDiablo2 package OpenDiablo2
import "github.com/essial/OpenDiablo2/Palettes"
// PaletteRGB represents a color in a palette
type PaletteRGB struct { type PaletteRGB struct {
R, G, B uint8 R, G, B uint8
} }
// Palette represents a palette // Palette represents a palette
type Palette struct { type Palette struct {
Name string Name Palettes.Palette
Colors [256]PaletteRGB Colors [256]PaletteRGB
} }
// CreatePalette creates a palette // CreatePalette creates a palette
func CreatePalette(name string, data []byte) Palette { func CreatePalette(name Palettes.Palette, data []byte) Palette {
result := Palette{Name: name} result := Palette{Name: name}
for i := 0; i <= 255; i++ { for i := 0; i <= 255; i++ {

43
Palettes/Palettes.go Normal file
View File

@ -0,0 +1,43 @@
package Palettes
// Palette represents a named palette
type Palette string
const (
// Act1 palette
Act1 Palette = "act1"
// Act2 palette
Act2 Palette = "act2"
// Act3 palette
Act3 Palette = "act3"
// Act4 palette
Act4 Palette = "act4"
// Act5 palette
Act5 Palette = "act5"
// EndGame palette
EndGame Palette = "endgame"
// EndGame2 palette
EndGame2 Palette = "endgame2"
// Fechar palette
Fechar Palette = "fechar"
// Loading palette
Loading Palette = "loading"
// Menu0 palette
Menu0 Palette = "menu0"
// Menu1 palette
Menu1 Palette = "menu1"
// Menu2 palette
Menu2 Palette = "menu2"
// Menu3 palette
Menu3 Palette = "menu3"
// Menu4 palette
Menu4 Palette = "menu4"
// Sky palette
Sky Palette = "sky"
// Static palette
Static Palette = "static"
// Trademark palette
Trademark Palette = "trademark"
// Units palette
Units Palette = "units"
)

View File

@ -3,119 +3,127 @@ package OpenDiablo2
import ( import (
"image/color" "image/color"
"github.com/essial/OpenDiablo2/Palettes"
"github.com/essial/OpenDiablo2/ResourcePaths" "github.com/essial/OpenDiablo2/ResourcePaths"
"github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten"
) )
// MainMenu represents the main menu
type MainMenu struct { type MainMenu struct {
Engine *Engine engine *Engine
TrademarkBackground Sprite trademarkBackground *Sprite
Background Sprite background *Sprite
DiabloLogoLeft Sprite diabloLogoLeft *Sprite
DiabloLogoRight Sprite diabloLogoRight *Sprite
DiabloLogoLeftBack Sprite diabloLogoLeftBack *Sprite
DiabloLogoRightBack Sprite diabloLogoRightBack *Sprite
CopyrightLabel *UILabel copyrightLabel *UILabel
CopyrightLabel2 *UILabel copyrightLabel2 *UILabel
ShowTrademarkScreen bool showTrademarkScreen bool
LeftButtonHeld bool leftButtonHeld bool
} }
// CreateMainMenu creates an instance of MainMenu
func CreateMainMenu(engine *Engine) *MainMenu { func CreateMainMenu(engine *Engine) *MainMenu {
result := &MainMenu{ result := &MainMenu{
Engine: engine, engine: engine,
ShowTrademarkScreen: true, showTrademarkScreen: true,
} }
return result return result
} }
// Load is called to load the resources for the main menu
func (v *MainMenu) Load() { func (v *MainMenu) Load() {
v.Engine.PlayBGM(ResourcePaths.BGMTitle) v.engine.PlayBGM(ResourcePaths.BGMTitle)
go func() { go func() {
loadStep := 1.0 / 8.0 loadStep := 1.0 / 8.0
v.Engine.LoadingProgress = 0 v.engine.LoadingProgress = 0
{ {
v.CopyrightLabel = CreateUILabel(v.Engine, ResourcePaths.FontFormal12, "static") v.copyrightLabel = CreateUILabel(v.engine, ResourcePaths.FontFormal12, Palettes.Static)
v.CopyrightLabel.Alignment = UILabelAlignCenter v.copyrightLabel.Alignment = UILabelAlignCenter
v.CopyrightLabel.SetText("Diablo 2 is © Copyright 2000-2016 Blizzard Entertainment") v.copyrightLabel.SetText("Diablo 2 is © Copyright 2000-2016 Blizzard Entertainment")
v.CopyrightLabel.ColorMod = color.RGBA{188, 168, 140, 255} v.copyrightLabel.ColorMod = color.RGBA{188, 168, 140, 255}
v.CopyrightLabel.MoveTo(400, 500) v.copyrightLabel.MoveTo(400, 500)
v.Engine.LoadingProgress += loadStep v.engine.LoadingProgress += loadStep
} }
{ {
v.CopyrightLabel2 = CreateUILabel(v.Engine, ResourcePaths.FontFormal12, "static") v.copyrightLabel2 = CreateUILabel(v.engine, ResourcePaths.FontFormal12, Palettes.Static)
v.CopyrightLabel2.Alignment = UILabelAlignCenter v.copyrightLabel2.Alignment = UILabelAlignCenter
v.CopyrightLabel2.SetText("All Rights Reserved.") v.copyrightLabel2.SetText("All Rights Reserved.")
v.CopyrightLabel2.ColorMod = color.RGBA{188, 168, 140, 255} v.copyrightLabel2.ColorMod = color.RGBA{188, 168, 140, 255}
v.CopyrightLabel2.MoveTo(400, 525) v.copyrightLabel2.MoveTo(400, 525)
v.Engine.LoadingProgress += loadStep v.engine.LoadingProgress += loadStep
} }
{ {
v.Background = v.Engine.LoadSprite(ResourcePaths.GameSelectScreen, v.Engine.Palettes["sky"]) v.background = v.engine.LoadSprite(ResourcePaths.GameSelectScreen, Palettes.Sky)
v.Background.MoveTo(0, 0) v.background.MoveTo(0, 0)
v.Engine.LoadingProgress += loadStep v.engine.LoadingProgress += loadStep
} }
{ {
v.TrademarkBackground = v.Engine.LoadSprite(ResourcePaths.TrademarkScreen, v.Engine.Palettes["sky"]) v.trademarkBackground = v.engine.LoadSprite(ResourcePaths.TrademarkScreen, Palettes.Sky)
v.TrademarkBackground.MoveTo(0, 0) v.trademarkBackground.MoveTo(0, 0)
v.Engine.LoadingProgress += loadStep v.engine.LoadingProgress += loadStep
} }
{ {
v.DiabloLogoLeft = v.Engine.LoadSprite(ResourcePaths.Diablo2LogoFireLeft, v.Engine.Palettes["units"]) v.diabloLogoLeft = v.engine.LoadSprite(ResourcePaths.Diablo2LogoFireLeft, Palettes.Units)
v.DiabloLogoLeft.Blend = true v.diabloLogoLeft.Blend = true
v.DiabloLogoLeft.Animate = true v.diabloLogoLeft.Animate = true
v.DiabloLogoLeft.MoveTo(400, 120) v.diabloLogoLeft.MoveTo(400, 120)
v.Engine.LoadingProgress += loadStep v.engine.LoadingProgress += loadStep
} }
{ {
v.DiabloLogoRight = v.Engine.LoadSprite(ResourcePaths.Diablo2LogoFireRight, v.Engine.Palettes["units"]) v.diabloLogoRight = v.engine.LoadSprite(ResourcePaths.Diablo2LogoFireRight, Palettes.Units)
v.DiabloLogoRight.Blend = true v.diabloLogoRight.Blend = true
v.DiabloLogoRight.Animate = true v.diabloLogoRight.Animate = true
v.DiabloLogoRight.MoveTo(400, 120) v.diabloLogoRight.MoveTo(400, 120)
v.Engine.LoadingProgress += loadStep v.engine.LoadingProgress += loadStep
} }
{ {
v.DiabloLogoLeftBack = v.Engine.LoadSprite(ResourcePaths.Diablo2LogoBlackLeft, v.Engine.Palettes["units"]) v.diabloLogoLeftBack = v.engine.LoadSprite(ResourcePaths.Diablo2LogoBlackLeft, Palettes.Units)
v.DiabloLogoLeftBack.MoveTo(400, 120) v.diabloLogoLeftBack.MoveTo(400, 120)
v.Engine.LoadingProgress += loadStep v.engine.LoadingProgress += loadStep
} }
{ {
v.DiabloLogoRightBack = v.Engine.LoadSprite(ResourcePaths.Diablo2LogoBlackRight, v.Engine.Palettes["units"]) v.diabloLogoRightBack = v.engine.LoadSprite(ResourcePaths.Diablo2LogoBlackRight, Palettes.Units)
v.DiabloLogoRightBack.MoveTo(400, 120) v.diabloLogoRightBack.MoveTo(400, 120)
v.Engine.LoadingProgress = 1.0 v.engine.LoadingProgress = 1.0
} }
}() }()
} }
// Unload unloads the data for the main menu
func (v *MainMenu) Unload() { func (v *MainMenu) Unload() {
} }
// Render renders the main menu
func (v *MainMenu) Render(screen *ebiten.Image) { func (v *MainMenu) Render(screen *ebiten.Image) {
if v.ShowTrademarkScreen { if v.showTrademarkScreen {
v.TrademarkBackground.DrawSegments(screen, 4, 3, 0) v.trademarkBackground.DrawSegments(screen, 4, 3, 0)
} else { } else {
v.Background.DrawSegments(screen, 4, 3, 0) v.background.DrawSegments(screen, 4, 3, 0)
} }
v.DiabloLogoLeftBack.Draw(screen) v.diabloLogoLeftBack.Draw(screen)
v.DiabloLogoRightBack.Draw(screen) v.diabloLogoRightBack.Draw(screen)
v.DiabloLogoLeft.Draw(screen) v.diabloLogoLeft.Draw(screen)
v.DiabloLogoRight.Draw(screen) v.diabloLogoRight.Draw(screen)
if v.ShowTrademarkScreen { if v.showTrademarkScreen {
v.CopyrightLabel.Draw(screen) v.copyrightLabel.Draw(screen)
v.CopyrightLabel2.Draw(screen) v.copyrightLabel2.Draw(screen)
} else { } else {
} }
} }
// Update runs the update logic on the main menu
func (v *MainMenu) Update() { func (v *MainMenu) Update() {
if v.ShowTrademarkScreen { if v.showTrademarkScreen {
if v.Engine.CursorButtonPressed(CursorButtonLeft) { if v.engine.CursorButtonPressed(CursorButtonLeft) {
v.LeftButtonHeld = true v.leftButtonHeld = true
v.ShowTrademarkScreen = false v.showTrademarkScreen = false
} }
return return
} }

View File

@ -8,6 +8,7 @@ import (
"github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten"
) )
// Sprite represents a type of object in D2 that is comprised of one or more frames and directions
type Sprite struct { type Sprite struct {
Directions uint32 Directions uint32
FramesPerDirection uint32 FramesPerDirection uint32
@ -18,8 +19,10 @@ type Sprite struct {
LastFrameTime time.Time LastFrameTime time.Time
Animate bool Animate bool
ColorMod color.Color ColorMod color.Color
visible bool
} }
// SpriteFrame represents a single frame of a sprite
type SpriteFrame struct { type SpriteFrame struct {
Flip uint32 Flip uint32
Width uint32 Width uint32
@ -33,8 +36,9 @@ type SpriteFrame struct {
Image *ebiten.Image Image *ebiten.Image
} }
func CreateSprite(data []byte, palette Palette) Sprite { // CreateSprite creates an instance of a sprite
result := Sprite{ func CreateSprite(data []byte, palette Palette) *Sprite {
result := &Sprite{
X: 50, X: 50,
Y: 50, Y: 50,
Frame: 0, Frame: 0,
@ -121,6 +125,7 @@ func CreateSprite(data []byte, palette Palette) Sprite {
return result return result
} }
// GetSize returns the size of the sprite
func (v *Sprite) GetSize() (uint32, uint32) { func (v *Sprite) GetSize() (uint32, uint32) {
frame := v.Frames[uint32(v.Frame)+(uint32(v.Direction)*v.FramesPerDirection)] frame := v.Frames[uint32(v.Frame)+(uint32(v.Direction)*v.FramesPerDirection)]
return frame.Width, frame.Height return frame.Width, frame.Height
@ -159,6 +164,7 @@ func (v *Sprite) Draw(target *ebiten.Image) {
target.DrawImage(frame.Image, opts) target.DrawImage(frame.Image, opts)
} }
// DrawSegments draws the sprite via a grid of segments
func (v *Sprite) DrawSegments(target *ebiten.Image, xSegments, ySegments, offset int) { func (v *Sprite) DrawSegments(target *ebiten.Image, xSegments, ySegments, offset int) {
v.updateAnimation() v.updateAnimation()
yOffset := int32(0) yOffset := int32(0)
@ -191,3 +197,8 @@ func (v *Sprite) MoveTo(x, y int) {
v.X = x v.X = x
v.Y = y v.Y = y
} }
// GetLocation returns the location of the sprite
func (v *Sprite) GetLocation() (int, int) {
return v.X, v.Y
}

8
UI/Button.go Normal file
View File

@ -0,0 +1,8 @@
package UI
// Button defines an object that acts like a button
type Button interface {
Widget
isPressed() bool
setPressed(bool)
}

12
UI/Widget.go Normal file
View File

@ -0,0 +1,12 @@
package UI
import (
"github.com/essial/OpenDiablo2/Common"
)
// Widget defines an object that is a UI widget
type Widget interface {
Common.Drawable
getEnabled() bool
setEnabled(bool)
}

View File

@ -3,6 +3,7 @@ package OpenDiablo2
import ( import (
"image/color" "image/color"
"github.com/essial/OpenDiablo2/Palettes"
"github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten"
) )
@ -27,7 +28,7 @@ type UILabel struct {
} }
// CreateUILabel creates a new instance of a UI label // CreateUILabel creates a new instance of a UI label
func CreateUILabel(engine *Engine, font, palette string) *UILabel { func CreateUILabel(engine *Engine, font string, palette Palettes.Palette) *UILabel {
result := &UILabel{ result := &UILabel{
Alignment: UILabelAlignLeft, Alignment: UILabelAlignLeft,
ColorMod: nil, ColorMod: nil,