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
type Drawable interface {
Draw(target *ebiten.Image)
GetSize() (uint32, uint32)
GetSize() (width, height uint32)
MoveTo(x, y int)
GetLocation() (int, int)
GetLocation() (x, y int)
GetVisible() bool
SetVisible(bool)
SetVisible(visible bool)
}

View File

@ -12,7 +12,7 @@ import (
type Sprite struct {
Directions uint32
FramesPerDirection uint32
Frames []SpriteFrame
Frames []*SpriteFrame
X, Y int
Frame, Direction uint8
Blend bool
@ -34,6 +34,7 @@ type SpriteFrame struct {
Length uint32
ImageData []int16
Image *ebiten.Image
Loaded bool
}
// 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])
dataPointer += 4
}
result.Frames = make([]SpriteFrame, totalFrames)
result.Frames = make([]*SpriteFrame, totalFrames)
for i := uint32(0); i < totalFrames; i++ {
dataPointer = framePointers[i]
result.Frames[i] = &SpriteFrame{}
result.Frames[i].Flip = binary.LittleEndian.Uint32(data[dataPointer : dataPointer+4])
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
}
result.Frames[ix].Image.ReplacePixels(newData)
result.Frames[ix].Loaded = true
}(i, dataPointer)
}
return result
@ -156,11 +158,16 @@ func (v *Sprite) Draw(target *ebiten.Image) {
float64((int32(v.Y) - int32(frame.Height) + frame.OffsetY)),
)
if v.Blend {
opts.CompositeMode = ebiten.CompositeModeLighter
opts.CompositeMode = ebiten.CompositeModeSourceOver
} else {
opts.CompositeMode = ebiten.CompositeModeSourceOver
}
if v.ColorMod != nil {
opts.ColorM = ColorToColorM(v.ColorMod)
}
for frame.Image == nil {
time.Sleep(time.Millisecond)
}
target.DrawImage(frame.Image, opts)
}
@ -180,10 +187,15 @@ func (v *Sprite) DrawSegments(target *ebiten.Image, xSegments, ySegments, offset
)
if v.Blend {
opts.CompositeMode = ebiten.CompositeModeLighter
} else {
opts.CompositeMode = ebiten.CompositeModeSourceOver
}
if v.ColorMod != nil {
opts.ColorM = ColorToColorM(v.ColorMod)
}
for frame.Image == nil {
time.Sleep(time.Millisecond)
}
target.DrawImage(frame.Image, opts)
xOffset += int32(frame.Width)
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
func (v *Engine) LoadFile(fileName string) []byte {
mutex.Lock()
// TODO: May want to cache some things if performance becomes an issue
mpqFile := v.Files[strings.ToLower(fileName)]
mpq, err := MPQ.Load(mpqFile)
@ -120,7 +123,7 @@ func (v *Engine) LoadFile(fileName string) []byte {
mpqStream := MPQ.CreateStream(mpq, blockTableEntry, fileName)
result := make([]byte, blockTableEntry.UncompressedFileSize)
mpqStream.Read(result, 0, blockTableEntry.UncompressedFileSize)
mutex.Unlock()
return result
}

View File

@ -23,6 +23,7 @@ type MainMenu struct {
diabloLogoRight *Common.Sprite
diabloLogoLeftBack *Common.Sprite
diabloLogoRightBack *Common.Sprite
exitDiabloButton *UI.Button
copyrightLabel *UI.Label
copyrightLabel2 *UI.Label
showTrademarkScreen bool
@ -48,14 +49,14 @@ func (v *MainMenu) Load() []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.Color = color.RGBA{188, 168, 140, 255}
v.copyrightLabel.MoveTo(400, 500)
},
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.Color = color.RGBA{188, 168, 140, 255}
v.copyrightLabel2.MoveTo(400, 525)
},
func() {
@ -86,6 +87,12 @@ func (v *MainMenu) Load() []func() {
v.diabloLogoRightBack = v.fileProvider.LoadSprite(ResourcePaths.Diablo2LogoBlackRight, Palettes.Units)
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) {
v.leftButtonHeld = true
v.showTrademarkScreen = false
v.exitDiabloButton.SetVisible(true)
}
return
}

View File

@ -1,8 +1,98 @@
package UI
// Button defines an object that acts like a button
type Button interface {
Widget
isPressed() bool
setPressed(bool)
import (
"image/color"
"github.com/essial/OpenDiablo2/Common"
"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
import (
"image/color"
"github.com/essial/OpenDiablo2/Common"
"github.com/essial/OpenDiablo2/Palettes"
"github.com/hajimehoshi/ebiten"
)
var fontCache = map[string]*Font{}
@ -15,8 +18,8 @@ type FontSize struct {
// Font represents a font
type Font struct {
FontSprite *Common.Sprite
Metrics map[uint8]FontSize
fontSprite *Common.Sprite
metrics map[uint8]FontSize
}
// 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
func CreateFont(font string, palette Palettes.Palette, fileProvider Common.FileProvider) *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"
fontData := fileProvider.LoadFile(font + ".tbl")
if string(fontData[0:5]) != woo {
@ -46,7 +49,34 @@ func CreateFont(font string, palette Palettes.Palette, fileProvider Common.FileP
Width: fontData[i+3],
Height: fontData[i+4],
}
result.Metrics[fontData[i+8]] = fontSize
result.metrics[fontData[i+8]] = fontSize
}
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
type Manager struct {
widgets []*Widget
widgets []Widget
cursorSprite *Common.Sprite
cursorButtons CursorButton
CursorX int
@ -29,7 +29,7 @@ type Manager struct {
// CreateManager creates a new instance of a UI manager
func CreateManager(provider Common.FileProvider) *Manager {
result := &Manager{
widgets: make([]*Widget, 0),
widgets: make([]Widget, 0),
cursorSprite: provider.LoadSprite(ResourcePaths.CursorDefault, Palettes.Units),
}
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
func (v *Manager) Reset() {
v.widgets = make([]*Widget, 0)
v.widgets = make([]Widget, 0)
}
// 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)
}
// Draw renders all of the UI elements
func (v *Manager) Draw(screen *ebiten.Image) {
for _, widget := range v.widgets {
if !widget.GetVisible() {
continue
}
widget.Draw(screen)
}
cx, cy := ebiten.CursorPosition()
v.cursorSprite.MoveTo(cx, cy)
v.cursorSprite.Draw(screen)

View File

@ -30,14 +30,14 @@ type Label struct {
Alignment LabelAlignment
font *Font
imageData *ebiten.Image
ColorMod color.Color
Color color.Color
}
// CreateLabel creates a new instance of a UI label
func CreateLabel(provider Common.FileProvider, font string, palette Palettes.Palette) *Label {
result := &Label{
Alignment: LabelAlignLeft,
ColorMod: nil,
Color: color.White,
font: GetFont(font, palette, provider),
}
@ -64,17 +64,6 @@ func (v *Label) Draw(target *ebiten.Image) {
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
func (v *Label) MoveTo(x, y int) {
v.X = x
@ -85,20 +74,11 @@ func (v *Label) cacheImage() {
if v.imageData != nil {
return
}
width, height := v.calculateSize()
width, height := v.font.GetTextMetrics(v.text)
v.Width = width
v.Height = height
v.imageData, _ = ebiten.NewImage(int(width), int(height), ebiten.FilterNearest)
x := uint32(0)
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)
}
v.font.Draw(0, 0, v.text, v.Color, v.imageData)
}
// SetText sets the label's text

View File

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