1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2025-02-10 02:26:29 -05:00
OpenDiablo2/d2core/d2systems/render.go
gravestench b86ac4df84 akara update: BaseComponent and BaseComponentMap
* common component methods have been put into BaseComponent and
BaseComponentMap
* boilerplate code for components has been significantly reduced
* all lint errors fixed in d2components
2020-12-07 12:44:11 -08:00

194 lines
5.1 KiB
Go

package d2systems
import (
"errors"
"time"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2config"
"github.com/gravestench/akara"
"github.com/hajimehoshi/ebiten/v2"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2components"
d2render "github.com/OpenDiablo2/OpenDiablo2/d2core/d2render/ebiten"
)
const (
gameTitle = "Open Diablo 2"
logPrefixRenderSystem = "Render System"
)
// NewRenderSystem creates a new render system
func NewRenderSystem() *RenderSystem {
viewports := akara.NewFilter().
Require(d2components.Viewport).
Require(d2components.MainViewport).
Require(d2components.Surface).
Build()
gameConfigs := akara.NewFilter().Require(d2components.GameConfig).Build()
r := &RenderSystem{
BaseSubscriberSystem: akara.NewBaseSubscriberSystem(viewports, gameConfigs),
Logger: d2util.NewLogger(),
}
r.SetPrefix(logPrefixRenderSystem)
return r
}
// static check that RenderSystem implements the System interface
var _ akara.System = &RenderSystem{}
// RenderSystem is responsible for rendering the main viewports of scenes
// to the game screen.
type RenderSystem struct {
*akara.BaseSubscriberSystem
*d2util.Logger
renderer d2interface.Renderer
viewports *akara.Subscription
configs *akara.Subscription
*d2components.GameConfigMap
*d2components.ViewportMap
*d2components.MainViewportMap
*d2components.RenderableMap
lastUpdate time.Time
gameLoopInitDelay time.Duration // there is a race condition, this is a hack
}
// Init initializes the system with the given world, injecting the necessary components
func (m *RenderSystem) Init(_ *akara.World) {
m.Info("initializing ...")
m.gameLoopInitDelay = time.Millisecond
m.viewports = m.Subscriptions[0]
m.configs = m.Subscriptions[1]
// try to inject the components we require, then cast the returned
// abstract ComponentMap back to the concrete implementation
m.GameConfigMap = m.InjectMap(d2components.GameConfig).(*d2components.GameConfigMap)
m.ViewportMap = m.InjectMap(d2components.Viewport).(*d2components.ViewportMap)
m.MainViewportMap = m.InjectMap(d2components.MainViewport).(*d2components.MainViewportMap)
m.RenderableMap = m.InjectMap(d2components.Surface).(*d2components.RenderableMap)
}
// Update will initialize the renderer, start the game loop, and
// disable the system (to prevent it from being called during the game loop).
//
// The reason why this isn't in the init step is because we use other systems
// for loading the config file, and it may take more than one iteration
func (m *RenderSystem) Update() {
if m.renderer != nil {
return // we already created the renderer
}
m.createRenderer()
if m.renderer == nil {
return // the renderer has not yet been created!
}
// if we have created the renderer, we can safely disable
// this system and start the run loop.
m.SetActive(false)
err := m.startGameLoop()
if err != nil {
m.Fatal(err.Error())
}
}
func (m *RenderSystem) createRenderer() {
m.Info("creating renderer instance")
configs := m.configs.GetEntities()
if len(configs) < 1 {
return
}
config, found := m.GetGameConfig(configs[0])
if !found {
return
}
// we should get rid of d2config.Configuration and use components instead...
oldStyleConfig := &d2config.Configuration{
MpqLoadOrder: config.MpqLoadOrder,
MpqPath: config.MpqPath,
TicksPerSecond: config.TicksPerSecond,
FpsCap: config.FpsCap,
SfxVolume: config.SfxVolume,
BgmVolume: config.BgmVolume,
FullScreen: config.FullScreen,
RunInBackground: config.RunInBackground,
VsyncEnabled: config.VsyncEnabled,
Backend: config.Backend,
LogLevel: config.LogLevel,
}
renderer, err := d2render.CreateRenderer(oldStyleConfig)
if err != nil {
m.Fatal(err.Error())
}
// HACK: hardcoded with ebiten for now
ebiten.SetCursorMode(ebiten.CursorModeHidden)
ebiten.SetFullscreen(config.FullScreen)
ebiten.SetRunnableOnUnfocused(config.RunInBackground)
ebiten.SetVsyncEnabled(config.VsyncEnabled)
ebiten.SetMaxTPS(config.TicksPerSecond)
m.renderer = renderer
}
func (m *RenderSystem) render(screen d2interface.Surface) error {
if m.gameLoopInitDelay > 0 {
return nil
}
for _, id := range m.viewports.GetEntities() {
vp, found := m.GetViewport(id)
if !found {
return errors.New("main viewport not found")
}
sfc, found := m.GetRenderable(id)
if !found {
return errors.New("main viewport doesn't have a surface")
}
if sfc.Surface == nil {
sfc.Surface = m.renderer.NewSurface(vp.Width, vp.Height)
}
screen.PushTranslation(vp.Left, vp.Top)
screen.Render(sfc.Surface)
screen.Pop()
}
return nil
}
func (m *RenderSystem) updateWorld() error {
currentTime := time.Now()
elapsed := currentTime.Sub(m.lastUpdate)
m.lastUpdate = currentTime
if m.gameLoopInitDelay > 0 {
m.gameLoopInitDelay -= elapsed
return nil
}
return m.World.Update(elapsed)
}
func (m *RenderSystem) startGameLoop() error {
m.Infof("starting game loop ...")
return m.renderer.Run(m.render, m.updateWorld, 800, 600, gameTitle)
}