mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2025-02-20 15:37:31 -05:00
* scene systems now render their objects to the main viewport * added SegmentedSprite component * added SegmentedSprite to sprite factory
343 lines
7.9 KiB
Go
343 lines
7.9 KiB
Go
package d2systems
|
|
|
|
import (
|
|
"path/filepath"
|
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math"
|
|
|
|
"github.com/gravestench/akara"
|
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2components"
|
|
)
|
|
|
|
const (
|
|
mainViewport int = 0
|
|
)
|
|
|
|
// NewBaseScene creates a new base scene instance
|
|
func NewBaseScene(key string) *BaseScene {
|
|
base := &BaseScene{
|
|
BaseSystem: &akara.BaseSystem{},
|
|
Logger: d2util.NewLogger(),
|
|
key: key,
|
|
Viewports: make([]akara.EID, 0),
|
|
GameObjects: make([]akara.EID, 0),
|
|
systems: &baseSystems{},
|
|
}
|
|
|
|
base.SetPrefix(key)
|
|
|
|
return base
|
|
}
|
|
|
|
var _ akara.System = &BaseScene{}
|
|
|
|
type baseSystems struct {
|
|
*RenderSystem
|
|
*GameObjectFactory
|
|
}
|
|
|
|
// BaseScene encapsulates common behaviors for systems that are considered "scenes",
|
|
// such as the main menu, the in-game map, the console, etc.
|
|
//
|
|
// The base scene is responsible for generic behaviors common to all scenes,
|
|
// like initializing the default viewport, or rendering game objects to the viewports.
|
|
type BaseScene struct {
|
|
*akara.BaseSystem
|
|
*d2util.Logger
|
|
key string
|
|
booted bool
|
|
paused bool
|
|
systems *baseSystems
|
|
Add *sceneObjectAssigner
|
|
Viewports []akara.EID
|
|
GameObjects []akara.EID
|
|
*d2components.MainViewportMap
|
|
*d2components.ViewportMap
|
|
*d2components.ViewportFilterMap
|
|
*d2components.CameraMap
|
|
*d2components.RenderableMap
|
|
*d2components.PositionMap
|
|
*d2components.ScaleMap
|
|
*d2components.AnimationMap
|
|
}
|
|
|
|
// Booted returns whether or not the scene has booted
|
|
func (s *BaseScene) Booted() bool {
|
|
return s.booted
|
|
}
|
|
|
|
// Paused returns whether or not the scene is paused
|
|
func (s *BaseScene) Paused() bool {
|
|
return s.paused
|
|
}
|
|
|
|
// Init the base scene
|
|
func (s *BaseScene) Init(world *akara.World) {
|
|
s.World = world
|
|
|
|
if s.World == nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
func (s *BaseScene) boot() {
|
|
s.Info("booting ...")
|
|
|
|
s.injectComponentMaps()
|
|
|
|
s.Add = &sceneObjectAssigner{BaseScene: s}
|
|
|
|
for idx := range s.Systems {
|
|
if rendersys, ok := s.Systems[idx].(*RenderSystem); ok {
|
|
s.systems.RenderSystem = rendersys
|
|
continue
|
|
}
|
|
|
|
if objFactory, ok := s.Systems[idx].(*GameObjectFactory); ok {
|
|
s.systems.GameObjectFactory = objFactory
|
|
continue
|
|
}
|
|
}
|
|
|
|
if s.systems.RenderSystem == nil {
|
|
s.Info("waiting for render system ...")
|
|
return
|
|
}
|
|
|
|
if s.systems.RenderSystem.renderer == nil {
|
|
s.Info("waiting for renderer instance ...")
|
|
return
|
|
}
|
|
|
|
if s.systems.GameObjectFactory == nil {
|
|
s.Info("waiting for game object factory ...")
|
|
return
|
|
}
|
|
|
|
s.systems.SpriteFactory.RenderSystem = s.systems.RenderSystem
|
|
|
|
s.createDefaultViewport()
|
|
|
|
s.Info("booted!")
|
|
s.booted = true
|
|
}
|
|
|
|
func (s *BaseScene) injectComponentMaps() {
|
|
s.MainViewportMap = s.World.InjectMap(d2components.MainViewport).(*d2components.MainViewportMap)
|
|
s.ViewportMap = s.World.InjectMap(d2components.Viewport).(*d2components.ViewportMap)
|
|
s.ViewportFilterMap = s.World.InjectMap(d2components.ViewportFilter).(*d2components.ViewportFilterMap)
|
|
s.CameraMap = s.World.InjectMap(d2components.Camera).(*d2components.CameraMap)
|
|
s.RenderableMap = s.World.InjectMap(d2components.Renderable).(*d2components.RenderableMap)
|
|
s.PositionMap = s.World.InjectMap(d2components.Position).(*d2components.PositionMap)
|
|
s.ScaleMap = s.World.InjectMap(d2components.Scale).(*d2components.ScaleMap)
|
|
s.AnimationMap = s.World.InjectMap(d2components.Animation).(*d2components.AnimationMap)
|
|
}
|
|
|
|
func (s *BaseScene) createDefaultViewport() {
|
|
s.Info("creating default viewport")
|
|
viewportID := s.NewEntity()
|
|
s.AddViewport(viewportID)
|
|
|
|
camera := s.AddCamera(viewportID)
|
|
camera.Width = 800
|
|
camera.Height = 600
|
|
camera.Zoom = 1
|
|
|
|
s.AddRenderable(viewportID).Surface = s.systems.renderer.NewSurface(camera.Width, camera.Height)
|
|
s.AddMainViewport(viewportID)
|
|
|
|
s.Viewports = append(s.Viewports, viewportID)
|
|
}
|
|
|
|
// Key returns the scene's key
|
|
func (s *BaseScene) Key() string {
|
|
return s.key
|
|
}
|
|
|
|
// Update performs scene boot and renders the scene viewports
|
|
func (s *BaseScene) Update() {
|
|
if !s.booted {
|
|
s.boot()
|
|
}
|
|
|
|
if !s.booted {
|
|
return
|
|
}
|
|
|
|
s.renderViewports()
|
|
}
|
|
|
|
func (s *BaseScene) renderViewports() {
|
|
if s.systems.RenderSystem == nil {
|
|
s.Warning("render system not present")
|
|
return
|
|
}
|
|
|
|
if s.systems.RenderSystem.renderer == nil {
|
|
s.Warning("render system doesn't have a renderer instance")
|
|
return
|
|
}
|
|
|
|
numViewports := len(s.Viewports)
|
|
|
|
if numViewports < 1 {
|
|
s.createDefaultViewport()
|
|
}
|
|
|
|
viewportObjects := s.binGameObjectsByViewport()
|
|
|
|
for idx := numViewports - 1; idx >= 0; idx-- {
|
|
s.renderViewport(idx, viewportObjects[idx])
|
|
}
|
|
}
|
|
|
|
func (s *BaseScene) binGameObjectsByViewport() map[int][]akara.EID {
|
|
bins := make(map[int][]akara.EID)
|
|
|
|
for _, eid := range s.GameObjects {
|
|
vpfilter, found := s.GetViewportFilter(eid)
|
|
if !found {
|
|
vpfilter = s.AddViewportFilter(eid)
|
|
vpfilter.Set(mainViewport, true)
|
|
}
|
|
|
|
for _, vpidx64 := range vpfilter.ToIntArray() {
|
|
vpidx := int(vpidx64)
|
|
|
|
_, found := bins[vpidx]
|
|
if !found {
|
|
bins[vpidx] = make([]akara.EID, 0)
|
|
}
|
|
|
|
bins[vpidx] = append(bins[vpidx], eid)
|
|
}
|
|
}
|
|
|
|
return bins
|
|
}
|
|
|
|
func (s *BaseScene) renderViewport(idx int, objects []akara.EID) {
|
|
id := s.Viewports[idx]
|
|
|
|
if idx == mainViewport {
|
|
s.AddMainViewport(id)
|
|
} else {
|
|
s.MainViewportMap.Remove(id)
|
|
}
|
|
|
|
camera, found := s.GetCamera(id)
|
|
if !found {
|
|
return
|
|
}
|
|
|
|
sfc, found := s.GetRenderable(id)
|
|
if !found {
|
|
return
|
|
}
|
|
|
|
if sfc.Surface == nil {
|
|
sfc.Surface = s.systems.renderer.NewSurface(camera.Width, camera.Height)
|
|
}
|
|
|
|
cx, cy := int(camera.X()), int(camera.Y())
|
|
|
|
sfc.Surface.PushTranslation(cx, cy) // negative because we're offsetting everything that gets rendered
|
|
sfc.Surface.PushScale(camera.Zoom, camera.Zoom)
|
|
|
|
for _, object := range objects {
|
|
s.renderObject(sfc.Surface, object)
|
|
}
|
|
|
|
sfc.Pop()
|
|
sfc.Pop()
|
|
}
|
|
|
|
func (s *BaseScene) renderObject(target d2interface.Surface, id akara.EID) {
|
|
renderable, found := s.GetRenderable(id)
|
|
if !found {
|
|
return
|
|
}
|
|
|
|
position, found := s.GetPosition(id)
|
|
if !found {
|
|
position = s.AddPosition(id)
|
|
}
|
|
|
|
scale, found := s.GetScale(id)
|
|
if !found {
|
|
scale = s.AddScale(id)
|
|
}
|
|
|
|
x, y := int(position.X()), int(position.Y())
|
|
|
|
target.PushTranslation(x, y)
|
|
defer target.Pop()
|
|
|
|
target.PushScale(scale.X(), scale.Y())
|
|
defer target.Pop()
|
|
|
|
segment, found := s.systems.SpriteFactory.GetSegmentedSprite(id)
|
|
if found {
|
|
animation, found := s.GetAnimation(id)
|
|
if !found {
|
|
return
|
|
}
|
|
|
|
var offsetY int
|
|
|
|
segmentsX, segmentsY := segment.Xsegments, segment.Ysegments
|
|
frameOffset := segment.FrameOffset
|
|
|
|
for y := 0; y < segmentsY; y++ {
|
|
var offsetX, maxFrameHeight int
|
|
|
|
for x := 0; x < segmentsX; x++ {
|
|
idx := x + y*segmentsX + frameOffset*segmentsX*segmentsY
|
|
if err := animation.SetCurrentFrame(idx); err != nil {
|
|
s.Error("SetCurrentFrame error" + err.Error())
|
|
}
|
|
|
|
target.PushTranslation(x+offsetX, y+offsetY)
|
|
target.Render(animation.GetCurrentFrameSurface())
|
|
target.Pop()
|
|
|
|
frameWidth, frameHeight := animation.GetCurrentFrameSize()
|
|
maxFrameHeight = d2math.MaxInt(maxFrameHeight, frameHeight)
|
|
offsetX += frameWidth - 1
|
|
}
|
|
|
|
offsetY += maxFrameHeight - 1
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
target.Render(renderable.Surface)
|
|
}
|
|
|
|
// responsible for wrapping the object factory calls and assigning the created object entity id's to the scene
|
|
type sceneObjectAssigner struct {
|
|
*BaseScene
|
|
}
|
|
|
|
func (s *sceneObjectAssigner) Sprite(x, y float64, imgPath, palPath string) akara.EID {
|
|
s.Infof("creating sprite: %s, %s", filepath.Base(imgPath), palPath)
|
|
|
|
eid := s.systems.SpriteFactory.Sprite(x, y, imgPath, palPath)
|
|
s.GameObjects = append(s.GameObjects, eid)
|
|
|
|
return eid
|
|
}
|
|
|
|
func (s *sceneObjectAssigner) SegmentedSprite(x, y float64, imgPath, palPath string, xseg, yseg, frame int) akara.EID {
|
|
s.Infof("creating segmented sprite: %s, %s", filepath.Base(imgPath), palPath)
|
|
|
|
eid := s.systems.SpriteFactory.SegmentedSprite(x, y, imgPath, palPath, xseg, yseg, frame)
|
|
s.GameObjects = append(s.GameObjects, eid)
|
|
|
|
return eid
|
|
}
|