1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-06-11 18:20:42 +00:00

Fixed font rendering. Added basic button stuff.

This commit is contained in:
Tim Sarbin 2019-10-25 22:20:36 -04:00
parent f485f803c8
commit b0de898fff
9 changed files with 180 additions and 50 deletions

View File

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

View File

@ -12,7 +12,7 @@ import (
type Sprite struct { type Sprite struct {
Directions uint32 Directions uint32
FramesPerDirection uint32 FramesPerDirection uint32
Frames []SpriteFrame Frames []*SpriteFrame
X, Y int X, Y int
Frame, Direction uint8 Frame, Direction uint8
Blend bool Blend bool
@ -34,6 +34,7 @@ type SpriteFrame struct {
Length uint32 Length uint32
ImageData []int16 ImageData []int16
Image *ebiten.Image Image *ebiten.Image
Loaded bool
} }
// CreateSprite creates an instance of a sprite // CreateSprite creates an instance of a sprite
@ -57,10 +58,10 @@ func CreateSprite(data []byte, palette Palette) *Sprite {
framePointers[i] = binary.LittleEndian.Uint32(data[dataPointer : dataPointer+4]) framePointers[i] = binary.LittleEndian.Uint32(data[dataPointer : dataPointer+4])
dataPointer += 4 dataPointer += 4
} }
result.Frames = make([]SpriteFrame, totalFrames) result.Frames = make([]*SpriteFrame, totalFrames)
for i := uint32(0); i < totalFrames; i++ { for i := uint32(0); i < totalFrames; i++ {
dataPointer = framePointers[i] dataPointer = framePointers[i]
result.Frames[i] = &SpriteFrame{}
result.Frames[i].Flip = binary.LittleEndian.Uint32(data[dataPointer : dataPointer+4]) result.Frames[i].Flip = binary.LittleEndian.Uint32(data[dataPointer : dataPointer+4])
dataPointer += 4 dataPointer += 4
result.Frames[i].Width = binary.LittleEndian.Uint32(data[dataPointer : dataPointer+4]) result.Frames[i].Width = binary.LittleEndian.Uint32(data[dataPointer : dataPointer+4])
@ -120,6 +121,7 @@ func CreateSprite(data []byte, palette Palette) *Sprite {
newData[(ii*4)+3] = 0xFF newData[(ii*4)+3] = 0xFF
} }
result.Frames[ix].Image.ReplacePixels(newData) result.Frames[ix].Image.ReplacePixels(newData)
result.Frames[ix].Loaded = true
}(i, dataPointer) }(i, dataPointer)
} }
return result return result
@ -156,11 +158,16 @@ func (v *Sprite) Draw(target *ebiten.Image) {
float64((int32(v.Y) - int32(frame.Height) + frame.OffsetY)), float64((int32(v.Y) - int32(frame.Height) + frame.OffsetY)),
) )
if v.Blend { if v.Blend {
opts.CompositeMode = ebiten.CompositeModeLighter opts.CompositeMode = ebiten.CompositeModeSourceOver
} else {
opts.CompositeMode = ebiten.CompositeModeSourceOver
} }
if v.ColorMod != nil { if v.ColorMod != nil {
opts.ColorM = ColorToColorM(v.ColorMod) opts.ColorM = ColorToColorM(v.ColorMod)
} }
for frame.Image == nil {
time.Sleep(time.Millisecond)
}
target.DrawImage(frame.Image, opts) target.DrawImage(frame.Image, opts)
} }
@ -180,10 +187,15 @@ func (v *Sprite) DrawSegments(target *ebiten.Image, xSegments, ySegments, offset
) )
if v.Blend { if v.Blend {
opts.CompositeMode = ebiten.CompositeModeLighter opts.CompositeMode = ebiten.CompositeModeLighter
} else {
opts.CompositeMode = ebiten.CompositeModeSourceOver
} }
if v.ColorMod != nil { if v.ColorMod != nil {
opts.ColorM = ColorToColorM(v.ColorMod) opts.ColorM = ColorToColorM(v.ColorMod)
} }
for frame.Image == nil {
time.Sleep(time.Millisecond)
}
target.DrawImage(frame.Image, opts) target.DrawImage(frame.Image, opts)
xOffset += int32(frame.Width) xOffset += int32(frame.Width)
biggestYOffset = MaxInt32(biggestYOffset, int32(frame.Height)) biggestYOffset = MaxInt32(biggestYOffset, int32(frame.Height))

View File

@ -104,8 +104,11 @@ func (v *Engine) mapMpqFiles() {
} }
} }
var mutex sync.Mutex
// LoadFile loads a file from the specified mpq and returns the data as a byte array // LoadFile loads a file from the specified mpq and returns the data as a byte array
func (v *Engine) LoadFile(fileName string) []byte { func (v *Engine) LoadFile(fileName string) []byte {
mutex.Lock()
// TODO: May want to cache some things if performance becomes an issue // TODO: May want to cache some things if performance becomes an issue
mpqFile := v.Files[strings.ToLower(fileName)] mpqFile := v.Files[strings.ToLower(fileName)]
mpq, err := MPQ.Load(mpqFile) mpq, err := MPQ.Load(mpqFile)
@ -120,7 +123,7 @@ func (v *Engine) LoadFile(fileName string) []byte {
mpqStream := MPQ.CreateStream(mpq, blockTableEntry, fileName) mpqStream := MPQ.CreateStream(mpq, blockTableEntry, fileName)
result := make([]byte, blockTableEntry.UncompressedFileSize) result := make([]byte, blockTableEntry.UncompressedFileSize)
mpqStream.Read(result, 0, blockTableEntry.UncompressedFileSize) mpqStream.Read(result, 0, blockTableEntry.UncompressedFileSize)
mutex.Unlock()
return result return result
} }

View File

@ -23,6 +23,7 @@ type MainMenu struct {
diabloLogoRight *Common.Sprite diabloLogoRight *Common.Sprite
diabloLogoLeftBack *Common.Sprite diabloLogoLeftBack *Common.Sprite
diabloLogoRightBack *Common.Sprite diabloLogoRightBack *Common.Sprite
exitDiabloButton *UI.Button
copyrightLabel *UI.Label copyrightLabel *UI.Label
copyrightLabel2 *UI.Label copyrightLabel2 *UI.Label
showTrademarkScreen bool showTrademarkScreen bool
@ -48,14 +49,14 @@ func (v *MainMenu) Load() []func() {
v.copyrightLabel = UI.CreateLabel(v.fileProvider, ResourcePaths.FontFormal12, Palettes.Static) v.copyrightLabel = UI.CreateLabel(v.fileProvider, ResourcePaths.FontFormal12, Palettes.Static)
v.copyrightLabel.Alignment = UI.LabelAlignCenter v.copyrightLabel.Alignment = UI.LabelAlignCenter
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.Color = color.RGBA{188, 168, 140, 255}
v.copyrightLabel.MoveTo(400, 500) v.copyrightLabel.MoveTo(400, 500)
}, },
func() { func() {
v.copyrightLabel2 = UI.CreateLabel(v.fileProvider, ResourcePaths.FontFormal12, Palettes.Static) v.copyrightLabel2 = UI.CreateLabel(v.fileProvider, ResourcePaths.FontFormal12, Palettes.Static)
v.copyrightLabel2.Alignment = UI.LabelAlignCenter v.copyrightLabel2.Alignment = UI.LabelAlignCenter
v.copyrightLabel2.SetText("All Rights Reserved.") v.copyrightLabel2.SetText("All Rights Reserved.")
v.copyrightLabel2.ColorMod = color.RGBA{188, 168, 140, 255} v.copyrightLabel2.Color = color.RGBA{188, 168, 140, 255}
v.copyrightLabel2.MoveTo(400, 525) v.copyrightLabel2.MoveTo(400, 525)
}, },
func() { func() {
@ -86,6 +87,12 @@ func (v *MainMenu) Load() []func() {
v.diabloLogoRightBack = v.fileProvider.LoadSprite(ResourcePaths.Diablo2LogoBlackRight, Palettes.Units) v.diabloLogoRightBack = v.fileProvider.LoadSprite(ResourcePaths.Diablo2LogoBlackRight, Palettes.Units)
v.diabloLogoRightBack.MoveTo(400, 120) v.diabloLogoRightBack.MoveTo(400, 120)
}, },
func() {
v.exitDiabloButton = UI.CreateButton(v.fileProvider, "EXIT DIABLO II")
v.exitDiabloButton.MoveTo(264, 535)
v.exitDiabloButton.SetVisible(false)
v.uiManager.AddWidget(v.exitDiabloButton)
},
} }
} }
@ -120,6 +127,7 @@ func (v *MainMenu) Update() {
if v.uiManager.CursorButtonPressed(UI.CursorButtonLeft) { if v.uiManager.CursorButtonPressed(UI.CursorButtonLeft) {
v.leftButtonHeld = true v.leftButtonHeld = true
v.showTrademarkScreen = false v.showTrademarkScreen = false
v.exitDiabloButton.SetVisible(true)
} }
return return
} }

View File

@ -1,8 +1,98 @@
package UI package UI
// Button defines an object that acts like a button import (
type Button interface { "image/color"
Widget
isPressed() bool "github.com/essial/OpenDiablo2/Common"
setPressed(bool) "github.com/essial/OpenDiablo2/Palettes"
"github.com/essial/OpenDiablo2/ResourcePaths"
"github.com/hajimehoshi/ebiten"
)
// Button defines a standard wide UI button
type Button struct {
enabled bool
x, y int
width, height uint32
visible bool
pressed bool
fileProvider Common.FileProvider
normalImage *ebiten.Image
pressedImage *ebiten.Image
}
// CreateButton creates an instance of Button
func CreateButton(fileProvider Common.FileProvider, text string) *Button {
result := &Button{
fileProvider: fileProvider,
width: 272,
height: 35,
visible: true,
enabled: true,
pressed: false,
}
font := GetFont(ResourcePaths.FontExocet10, Palettes.Units, fileProvider)
result.normalImage, _ = ebiten.NewImage(272, 35, ebiten.FilterNearest)
result.pressedImage, _ = ebiten.NewImage(272, 35, ebiten.FilterNearest)
textWidth, textHeight := font.GetTextMetrics(text)
textX := (272 / 2) - (textWidth / 2)
textY := (35 / 2) - (textHeight / 2) + 5
buttonSprite := fileProvider.LoadSprite(ResourcePaths.WideButtonBlank, Palettes.Units)
buttonSprite.MoveTo(0, 0)
buttonSprite.Blend = true
buttonSprite.DrawSegments(result.normalImage, 2, 1, 0)
font.Draw(int(textX), int(textY), text, color.RGBA{100, 100, 100, 255}, result.normalImage)
buttonSprite.DrawSegments(result.pressedImage, 2, 1, 1)
font.Draw(int(textX-2), int(textY+2), text, color.Black, result.pressedImage)
return result
}
// Draw renders the button
func (v *Button) Draw(target *ebiten.Image) {
opts := &ebiten.DrawImageOptions{
CompositeMode: ebiten.CompositeModeSourceAtop,
Filter: ebiten.FilterNearest,
}
opts.GeoM.Translate(float64(v.x), float64(v.y))
if v.pressed {
target.DrawImage(v.pressedImage, opts)
return
}
target.DrawImage(v.normalImage, opts)
}
// GetEnabled returns the enabled state
func (v *Button) GetEnabled() bool {
return v.enabled
}
// SetEnabled sets the enabled state
func (v *Button) SetEnabled(enabled bool) {
v.enabled = enabled
}
// GetSize returns the size of the button
func (v *Button) GetSize() (uint32, uint32) {
return v.width, v.height
}
// MoveTo moves the button
func (v *Button) MoveTo(x, y int) {
v.x = x
v.y = y
}
// GetLocation returns the location of the button
func (v *Button) GetLocation() (x, y int) {
return v.x, v.y
}
// GetVisible returns the visibility of the button
func (v *Button) GetVisible() bool {
return v.visible
}
// SetVisible sets the visibility of the button
func (v *Button) SetVisible(visible bool) {
v.visible = visible
} }

View File

@ -1,8 +1,11 @@
package UI package UI
import ( import (
"image/color"
"github.com/essial/OpenDiablo2/Common" "github.com/essial/OpenDiablo2/Common"
"github.com/essial/OpenDiablo2/Palettes" "github.com/essial/OpenDiablo2/Palettes"
"github.com/hajimehoshi/ebiten"
) )
var fontCache = map[string]*Font{} var fontCache = map[string]*Font{}
@ -15,8 +18,8 @@ type FontSize struct {
// Font represents a font // Font represents a font
type Font struct { type Font struct {
FontSprite *Common.Sprite fontSprite *Common.Sprite
Metrics map[uint8]FontSize metrics map[uint8]FontSize
} }
// GetFont creates or loads an existing font // GetFont creates or loads an existing font
@ -33,9 +36,9 @@ func GetFont(font string, palette Palettes.Palette, fileProvider Common.FileProv
// CreateFont creates an instance of a MPQ Font // CreateFont creates an instance of a MPQ Font
func CreateFont(font string, palette Palettes.Palette, fileProvider Common.FileProvider) *Font { func CreateFont(font string, palette Palettes.Palette, fileProvider Common.FileProvider) *Font {
result := &Font{ result := &Font{
Metrics: make(map[uint8]FontSize), metrics: make(map[uint8]FontSize),
} }
result.FontSprite = fileProvider.LoadSprite(font+".dc6", palette) result.fontSprite = fileProvider.LoadSprite(font+".dc6", palette)
woo := "Woo!\x01" woo := "Woo!\x01"
fontData := fileProvider.LoadFile(font + ".tbl") fontData := fileProvider.LoadFile(font + ".tbl")
if string(fontData[0:5]) != woo { if string(fontData[0:5]) != woo {
@ -46,7 +49,34 @@ func CreateFont(font string, palette Palettes.Palette, fileProvider Common.FileP
Width: fontData[i+3], Width: fontData[i+3],
Height: fontData[i+4], Height: fontData[i+4],
} }
result.Metrics[fontData[i+8]] = fontSize result.metrics[fontData[i+8]] = fontSize
} }
return result return result
} }
// GetTextMetrics returns the size of the specified text
func (v *Font) GetTextMetrics(text string) (width, height uint32) {
width = uint32(0)
height = uint32(0)
for _, ch := range text {
metric := v.metrics[uint8(ch)]
width += uint32(metric.Width)
height = Common.Max(height, uint32(metric.Height))
}
return
}
// Draw draws the font on the target surface
func (v *Font) Draw(x, y int, text string, color color.Color, target *ebiten.Image) {
v.fontSprite.ColorMod = color
v.fontSprite.Blend = true
_, height := v.GetTextMetrics(text)
for _, ch := range text {
char := uint8(ch)
metric := v.metrics[char]
v.fontSprite.Frame = char
v.fontSprite.MoveTo(x, y+int(height))
v.fontSprite.Draw(target)
x += int(metric.Width)
}
}

View File

@ -19,7 +19,7 @@ const (
// Manager represents the UI manager // Manager represents the UI manager
type Manager struct { type Manager struct {
widgets []*Widget widgets []Widget
cursorSprite *Common.Sprite cursorSprite *Common.Sprite
cursorButtons CursorButton cursorButtons CursorButton
CursorX int CursorX int
@ -29,7 +29,7 @@ type Manager struct {
// CreateManager creates a new instance of a UI manager // CreateManager creates a new instance of a UI manager
func CreateManager(provider Common.FileProvider) *Manager { func CreateManager(provider Common.FileProvider) *Manager {
result := &Manager{ result := &Manager{
widgets: make([]*Widget, 0), widgets: make([]Widget, 0),
cursorSprite: provider.LoadSprite(ResourcePaths.CursorDefault, Palettes.Units), cursorSprite: provider.LoadSprite(ResourcePaths.CursorDefault, Palettes.Units),
} }
return result return result
@ -37,16 +37,23 @@ func CreateManager(provider Common.FileProvider) *Manager {
// Reset resets the state of the UI manager. Typically called for new scenes // Reset resets the state of the UI manager. Typically called for new scenes
func (v *Manager) Reset() { func (v *Manager) Reset() {
v.widgets = make([]*Widget, 0) v.widgets = make([]Widget, 0)
} }
// AddWidget adds a widget to the UI manager // AddWidget adds a widget to the UI manager
func (v *Manager) AddWidget(widget *Widget) { func (v *Manager) AddWidget(widget Widget) {
v.widgets = append(v.widgets, widget) v.widgets = append(v.widgets, widget)
} }
// Draw renders all of the UI elements // Draw renders all of the UI elements
func (v *Manager) Draw(screen *ebiten.Image) { func (v *Manager) Draw(screen *ebiten.Image) {
for _, widget := range v.widgets {
if !widget.GetVisible() {
continue
}
widget.Draw(screen)
}
cx, cy := ebiten.CursorPosition() cx, cy := ebiten.CursorPosition()
v.cursorSprite.MoveTo(cx, cy) v.cursorSprite.MoveTo(cx, cy)
v.cursorSprite.Draw(screen) v.cursorSprite.Draw(screen)

View File

@ -30,14 +30,14 @@ type Label struct {
Alignment LabelAlignment Alignment LabelAlignment
font *Font font *Font
imageData *ebiten.Image imageData *ebiten.Image
ColorMod color.Color Color color.Color
} }
// CreateLabel creates a new instance of a UI label // CreateLabel creates a new instance of a UI label
func CreateLabel(provider Common.FileProvider, font string, palette Palettes.Palette) *Label { func CreateLabel(provider Common.FileProvider, font string, palette Palettes.Palette) *Label {
result := &Label{ result := &Label{
Alignment: LabelAlignLeft, Alignment: LabelAlignLeft,
ColorMod: nil, Color: color.White,
font: GetFont(font, palette, provider), font: GetFont(font, palette, provider),
} }
@ -64,17 +64,6 @@ func (v *Label) Draw(target *ebiten.Image) {
target.DrawImage(v.imageData, opts) target.DrawImage(v.imageData, opts)
} }
func (v *Label) calculateSize() (uint32, uint32) {
width := uint32(0)
height := uint32(0)
for _, ch := range v.text {
metric := v.font.Metrics[uint8(ch)]
width += uint32(metric.Width)
height = Common.Max(height, uint32(metric.Height))
}
return width, height
}
// MoveTo moves the label to the specified location // MoveTo moves the label to the specified location
func (v *Label) MoveTo(x, y int) { func (v *Label) MoveTo(x, y int) {
v.X = x v.X = x
@ -85,20 +74,11 @@ func (v *Label) cacheImage() {
if v.imageData != nil { if v.imageData != nil {
return return
} }
width, height := v.calculateSize() width, height := v.font.GetTextMetrics(v.text)
v.Width = width v.Width = width
v.Height = height v.Height = height
v.imageData, _ = ebiten.NewImage(int(width), int(height), ebiten.FilterNearest) v.imageData, _ = ebiten.NewImage(int(width), int(height), ebiten.FilterNearest)
x := uint32(0) v.font.Draw(0, 0, v.text, v.Color, v.imageData)
v.font.FontSprite.ColorMod = v.ColorMod
for _, ch := range v.text {
char := uint8(ch)
metric := v.font.Metrics[char]
v.font.FontSprite.Frame = char
v.font.FontSprite.MoveTo(int(x), int(height))
v.font.FontSprite.Draw(v.imageData)
x += uint32(metric.Width)
}
} }
// SetText sets the label's text // SetText sets the label's text

View File

@ -7,6 +7,6 @@ import (
// Widget defines an object that is a UI widget // Widget defines an object that is a UI widget
type Widget interface { type Widget interface {
Common.Drawable Common.Drawable
getEnabled() bool GetEnabled() bool
setEnabled(bool) SetEnabled(enabled bool)
} }