2020-01-31 23:18:11 -05:00
|
|
|
package ebiten
|
|
|
|
|
|
|
|
import (
|
2020-10-28 14:17:42 -04:00
|
|
|
"errors"
|
2020-01-31 23:18:11 -05:00
|
|
|
"image"
|
|
|
|
|
2020-10-28 14:17:42 -04:00
|
|
|
"github.com/hajimehoshi/ebiten/v2"
|
|
|
|
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
2020-07-23 12:56:50 -04:00
|
|
|
|
2020-07-08 09:16:16 -04:00
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
2020-06-29 00:41:58 -04:00
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
2020-07-08 09:16:16 -04:00
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2config"
|
2020-01-31 23:18:11 -05:00
|
|
|
)
|
|
|
|
|
2020-07-17 18:51:44 -04:00
|
|
|
const (
|
2020-08-05 22:04:36 -04:00
|
|
|
screenWidth = 800
|
|
|
|
screenHeight = 600
|
|
|
|
defaultSaturation = 1.0
|
|
|
|
defaultBrightness = 1.0
|
|
|
|
defaultSkewX = 0.0
|
|
|
|
defaultSkewY = 0.0
|
|
|
|
defaultScaleX = 1.0
|
|
|
|
defaultScaleY = 1.0
|
2020-07-17 18:51:44 -04:00
|
|
|
)
|
|
|
|
|
2020-10-28 14:17:42 -04:00
|
|
|
type renderCallback = func(surface d2interface.Surface) error
|
|
|
|
|
|
|
|
type updateCallback = func() error
|
|
|
|
|
|
|
|
// static check that we implement our renderer interface
|
|
|
|
var _ d2interface.Renderer = &Renderer{}
|
|
|
|
|
2020-07-17 18:51:44 -04:00
|
|
|
// Renderer is an implementation of a renderer
|
2020-06-19 02:19:27 -04:00
|
|
|
type Renderer struct {
|
2020-10-28 14:17:42 -04:00
|
|
|
updateCallback
|
|
|
|
renderCallback
|
|
|
|
lastRenderError error
|
2020-06-19 02:19:27 -04:00
|
|
|
}
|
|
|
|
|
2020-10-28 14:17:42 -04:00
|
|
|
// Update calls the game's logical update function (the `Advance` method)
|
|
|
|
func (r *Renderer) Update() error {
|
|
|
|
if r.updateCallback == nil {
|
|
|
|
return errors.New("no update callback defined for ebiten renderer")
|
2020-06-19 02:19:27 -04:00
|
|
|
}
|
2020-07-17 18:51:44 -04:00
|
|
|
|
2020-10-28 14:17:42 -04:00
|
|
|
return r.updateCallback()
|
|
|
|
}
|
|
|
|
|
|
|
|
const drawError = "no render callback defined for ebiten renderer"
|
|
|
|
|
|
|
|
// Draw updates the screen with the given *ebiten.Image
|
|
|
|
func (r *Renderer) Draw(screen *ebiten.Image) {
|
|
|
|
r.lastRenderError = nil
|
|
|
|
|
|
|
|
if r.renderCallback == nil {
|
|
|
|
r.lastRenderError = errors.New(drawError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
r.lastRenderError = r.renderCallback(createEbitenSurface(r, screen))
|
2020-06-19 02:19:27 -04:00
|
|
|
}
|
|
|
|
|
2020-07-17 18:51:44 -04:00
|
|
|
// Layout returns the renderer screen width and height
|
|
|
|
func (r *Renderer) Layout(_, _ int) (width, height int) {
|
|
|
|
return screenWidth, screenHeight
|
2020-06-19 02:19:27 -04:00
|
|
|
}
|
2020-01-31 23:18:11 -05:00
|
|
|
|
2020-07-17 18:51:44 -04:00
|
|
|
// CreateRenderer creates an ebiten renderer instance
|
2020-11-03 07:54:15 -05:00
|
|
|
func CreateRenderer(cfg *d2config.Configuration) (*Renderer, error) {
|
2020-02-01 21:51:49 -05:00
|
|
|
result := &Renderer{}
|
2020-01-31 23:18:11 -05:00
|
|
|
|
2020-11-03 07:54:15 -05:00
|
|
|
if cfg != nil {
|
|
|
|
config := cfg
|
2020-10-28 21:02:12 -04:00
|
|
|
|
|
|
|
ebiten.SetCursorMode(ebiten.CursorModeHidden)
|
|
|
|
ebiten.SetFullscreen(config.FullScreen)
|
|
|
|
ebiten.SetRunnableOnUnfocused(config.RunInBackground)
|
|
|
|
ebiten.SetVsyncEnabled(config.VsyncEnabled)
|
|
|
|
ebiten.SetMaxTPS(config.TicksPerSecond)
|
|
|
|
}
|
2020-01-31 23:18:11 -05:00
|
|
|
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
2020-07-17 18:51:44 -04:00
|
|
|
// GetRendererName returns the name of the renderer
|
2020-02-01 21:51:49 -05:00
|
|
|
func (*Renderer) GetRendererName() string {
|
2020-01-31 23:18:11 -05:00
|
|
|
return "Ebiten"
|
|
|
|
}
|
|
|
|
|
2020-07-17 18:51:44 -04:00
|
|
|
// SetWindowIcon sets the icon for the window, visible in the chrome of the window
|
2020-02-01 21:51:49 -05:00
|
|
|
func (*Renderer) SetWindowIcon(fileName string) {
|
2020-10-28 14:17:42 -04:00
|
|
|
_, iconImage, err := ebitenutil.NewImageFromFile(fileName)
|
2020-01-31 23:18:11 -05:00
|
|
|
if err == nil {
|
|
|
|
ebiten.SetWindowIcon([]image.Image{iconImage})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-17 18:51:44 -04:00
|
|
|
// IsDrawingSkipped returns a bool for whether or not the drawing has been skipped
|
2020-02-01 21:51:49 -05:00
|
|
|
func (r *Renderer) IsDrawingSkipped() bool {
|
2020-10-28 14:17:42 -04:00
|
|
|
return r.lastRenderError != nil
|
2020-01-31 23:18:11 -05:00
|
|
|
}
|
|
|
|
|
2020-07-17 18:51:44 -04:00
|
|
|
// Run initializes the renderer
|
2020-10-28 14:17:42 -04:00
|
|
|
func (r *Renderer) Run(f renderCallback, u updateCallback, width, height int, title string) error {
|
2020-06-19 02:19:27 -04:00
|
|
|
r.renderCallback = f
|
2020-10-28 14:17:42 -04:00
|
|
|
r.updateCallback = u
|
2020-07-17 18:51:44 -04:00
|
|
|
|
2020-06-19 02:19:27 -04:00
|
|
|
ebiten.SetWindowTitle(title)
|
|
|
|
ebiten.SetWindowResizable(true)
|
|
|
|
ebiten.SetWindowSize(width, height)
|
2020-07-17 18:51:44 -04:00
|
|
|
|
2020-06-19 02:19:27 -04:00
|
|
|
return ebiten.RunGame(r)
|
2020-01-31 23:18:11 -05:00
|
|
|
}
|
|
|
|
|
2020-07-17 18:51:44 -04:00
|
|
|
// CreateSurface creates a renderer surface from an existing surface
|
2020-06-29 00:41:58 -04:00
|
|
|
func (r *Renderer) CreateSurface(surface d2interface.Surface) (d2interface.Surface, error) {
|
Decouple asset manager from renderer (#730)
* improve AssetManager implementation
Notable changes are:
* removed the individual managers inside of d2asset, only one asset manager
* AssetManager now has caches for the types of files it loads
* created a type for TextDictionary (the txt file structs)
* fixed a file path bug in d2loader Source
* fixed a asset stream bug in d2loader Asset
* d2loader.Loader now needs a d2config.Config on creation (for resolving locale files)
* updated the mpq file in d2asset test data, added test case for "sub-directory"
* added a Data method to d2asset.Asset. The data is cached on first full read.
* renamed ArchiveDataStream to DataStream in d2interface
* moved palette utility func out of d2asset and into d2util
* bugfix for MacOS mpq loader issue
* lint fixes, added data caching to filesystem asset
* adding comment for mpq asset close
* Decouple d2asset from d2render
Notable changes in d2common:
* d2dcc.Load now fully decodes the dcc and stores the directions/frames in the dcc struct
* un-exported dcc.decodeDirection, it is only used in d2dcc
* removed font interface from d2interface, we only have one font implementation
* added `Renderer` method to d2interface.Surface, animations use this to bind to a renderer and create surfaces as they need
* added `BindRenderer` method to animation interface
Notable changes in d2common/d2asset:
* **d2asset.NewAssetManager only needs to be passed a d2config.Config**, it is decoupled from d2render
* exported Animation
* Animation implementation binds to the renderer to create surfaces only on the first time it is rendered
* font, dcc, dc6 initialization logic moved out of asset_manager.go
* for dc6 and dcc animations, the process of decoding and creating render surfaces has been broken into different methods
* the d2asset.Font struct now stores font table data for initialization purposes
Notable changes in d2core/d2render:
* Surfaces store a renderer reference, this allows animations to bind to the renderer and create a surface just-in-time
**These last changes should have been a separate PR, sorry.**
Notable changes in d2core/d2ui:
* ui.NewSprite now handles creating an animation internally, only needs image and palette path as arguments
Notable Changes in d2game:
Because of the change in d2ui, all instances of this code pattern...
```golang
animation, err := screen.asset.LoadAnimation(imgPath, palettePath)
sprite, err := screen.ui.NewSprite(animation)
```
... becomes this ...
```golang
sprite, err := screen.ui.NewSprite(imgPath, palettePath)
```
2020-09-14 17:31:45 -04:00
|
|
|
img := surface.(*ebitenSurface).image
|
|
|
|
sfcState := surfaceState{
|
|
|
|
filter: ebiten.FilterNearest,
|
|
|
|
effect: d2enum.DrawEffectNone,
|
|
|
|
saturation: defaultSaturation,
|
|
|
|
brightness: defaultBrightness,
|
|
|
|
skewX: defaultSkewX,
|
|
|
|
skewY: defaultSkewY,
|
|
|
|
scaleX: defaultScaleX,
|
|
|
|
scaleY: defaultScaleY,
|
|
|
|
}
|
|
|
|
result := createEbitenSurface(r, img, sfcState)
|
2020-07-07 08:57:40 -04:00
|
|
|
|
2020-02-09 14:12:04 -05:00
|
|
|
return result, nil
|
2020-01-31 23:18:11 -05:00
|
|
|
}
|
|
|
|
|
2020-07-17 18:51:44 -04:00
|
|
|
// NewSurface creates a new surface
|
2020-10-28 14:17:42 -04:00
|
|
|
func (r *Renderer) NewSurface(width, height int) d2interface.Surface {
|
|
|
|
img := ebiten.NewImage(width, height)
|
2020-07-17 18:51:44 -04:00
|
|
|
|
2020-10-28 14:17:42 -04:00
|
|
|
return createEbitenSurface(r, img)
|
2020-01-31 23:18:11 -05:00
|
|
|
}
|
|
|
|
|
2020-07-17 18:51:44 -04:00
|
|
|
// IsFullScreen returns a boolean for whether or not the renderer is currently set to fullscreen
|
2020-02-09 14:12:04 -05:00
|
|
|
func (r *Renderer) IsFullScreen() bool {
|
|
|
|
return ebiten.IsFullscreen()
|
2020-01-31 23:18:11 -05:00
|
|
|
}
|
|
|
|
|
2020-07-17 18:51:44 -04:00
|
|
|
// SetFullScreen sets the renderer to fullscreen, given a boolean
|
2020-02-09 14:12:04 -05:00
|
|
|
func (r *Renderer) SetFullScreen(fullScreen bool) {
|
2020-01-31 23:18:11 -05:00
|
|
|
ebiten.SetFullscreen(fullScreen)
|
|
|
|
}
|
|
|
|
|
2020-07-17 18:51:44 -04:00
|
|
|
// SetVSyncEnabled enables vsync, given a boolean
|
2020-02-09 14:12:04 -05:00
|
|
|
func (r *Renderer) SetVSyncEnabled(vsync bool) {
|
2020-01-31 23:18:11 -05:00
|
|
|
ebiten.SetVsyncEnabled(vsync)
|
|
|
|
}
|
|
|
|
|
2020-07-17 18:51:44 -04:00
|
|
|
// GetVSyncEnabled returns a boolean for whether or not vsync is enabled
|
2020-02-09 14:12:04 -05:00
|
|
|
func (r *Renderer) GetVSyncEnabled() bool {
|
|
|
|
return ebiten.IsVsyncEnabled()
|
2020-01-31 23:18:11 -05:00
|
|
|
}
|
|
|
|
|
2020-07-17 18:51:44 -04:00
|
|
|
// GetCursorPos returns the current cursor position x,y coordinates
|
|
|
|
func (r *Renderer) GetCursorPos() (x, y int) {
|
2020-02-09 14:12:04 -05:00
|
|
|
return ebiten.CursorPosition()
|
2020-01-31 23:18:11 -05:00
|
|
|
}
|
|
|
|
|
2020-07-17 18:51:44 -04:00
|
|
|
// CurrentFPS returns the current frames per second of the renderer
|
2020-02-01 21:51:49 -05:00
|
|
|
func (r *Renderer) CurrentFPS() float64 {
|
2020-01-31 23:18:11 -05:00
|
|
|
return ebiten.CurrentFPS()
|
|
|
|
}
|
2020-10-28 21:02:12 -04:00
|
|
|
|
|
|
|
// ShowPanicScreen shows a panic message in a forever loop
|
|
|
|
func (r *Renderer) ShowPanicScreen(message string) {
|
|
|
|
errorScreen := CreatePanicScreen(message)
|
|
|
|
|
|
|
|
err := ebiten.RunGame(errorScreen)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
}
|