1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2025-02-09 10:06:35 -05:00

scene rendering now works

* scene systems now render their objects to the main viewport
* added SegmentedSprite component
* added SegmentedSprite to sprite factory
This commit is contained in:
gravestench 2020-11-22 16:34:46 -08:00
parent 1c8240d869
commit 05dae775e4
7 changed files with 194 additions and 25 deletions

View File

@ -36,4 +36,5 @@ const (
SizeCID
AnimationCID
ScaleCID
SegmentedSpriteCID
)

View File

@ -19,7 +19,7 @@ type RenderableComponent struct {
d2interface.Surface
}
// RenderableMap is a map of entity ID's to Surface
// RenderableMap is a map of entity ID's to Renderable
type RenderableMap struct {
*akara.BaseComponentMap
}
@ -41,8 +41,8 @@ func (cm *RenderableMap) GetRenderable(id akara.EID) (*RenderableComponent, bool
return entry.(*RenderableComponent), found
}
// Surface is a convenient reference to be used as a component identifier
var Surface = newRenderable() // nolint:gochecknoglobals // global by design
// Renderable is a convenient reference to be used as a component identifier
var Renderable = newRenderable() // nolint:gochecknoglobals // global by design
func newRenderable() akara.Component {
return &RenderableComponent{

View File

@ -0,0 +1,67 @@
//nolint:dupl,golint,stylecheck // component declarations are supposed to look the same
package d2components
import (
"github.com/gravestench/akara"
)
// static check that SegmentedSpriteComponent implements Component
var _ akara.Component = &SegmentedSpriteComponent{}
// static check that SegmentedSpriteMap implements ComponentMap
var _ akara.ComponentMap = &SegmentedSpriteMap{}
// SegmentedSpriteComponent represents an entities x,y axis scale as a vector
type SegmentedSpriteComponent struct {
*akara.BaseComponent
Xsegments int
Ysegments int
FrameOffset int
}
// SegmentedSpriteMap is a map of entity ID's to SegmentedSprite
type SegmentedSpriteMap struct {
*akara.BaseComponentMap
}
// AddSegmentedSprite adds a new SegmentedSpriteComponent for the given entity id and returns it.
// this is a convenience method for the generic Add method, as it returns a
// *SegmentedSpriteComponent instead of an akara.Component
func (cm *SegmentedSpriteMap) AddSegmentedSprite(id akara.EID) *SegmentedSpriteComponent {
c := cm.Add(id).(*SegmentedSpriteComponent)
c.Xsegments = 1
c.Ysegments = 1
return c
}
// GetSegmentedSprite returns the SegmentedSpriteComponent associated with the given entity id
func (cm *SegmentedSpriteMap) GetSegmentedSprite(id akara.EID) (*SegmentedSpriteComponent, bool) {
entry, found := cm.Get(id)
if entry == nil {
return nil, false
}
return entry.(*SegmentedSpriteComponent), found
}
// SegmentedSprite is a convenient reference to be used as a component identifier
var SegmentedSprite = newSegmentedSprite() // nolint:gochecknoglobals // global by design
func newSegmentedSprite() akara.Component {
return &SegmentedSpriteComponent{
BaseComponent: akara.NewBaseComponent(SegmentedSpriteCID, newSegmentedSprite, newSegmentedSpriteMap),
}
}
func newSegmentedSpriteMap() akara.ComponentMap {
baseComponent := akara.NewBaseComponent(SegmentedSpriteCID, newSegmentedSprite, newSegmentedSpriteMap)
baseMap := akara.NewBaseComponentMap(baseComponent)
cm := &SegmentedSpriteMap{
BaseComponentMap: baseMap,
}
return cm
}

View File

@ -25,7 +25,7 @@ func NewRenderSystem() *RenderSystem {
viewports := akara.NewFilter().
Require(d2components.Viewport).
Require(d2components.MainViewport).
Require(d2components.Surface).
Require(d2components.Renderable).
Build()
gameConfigs := akara.NewFilter().Require(d2components.GameConfig).Build()
@ -73,7 +73,7 @@ func (m *RenderSystem) Init(_ *akara.World) {
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)
m.RenderableMap = m.InjectMap(d2components.Renderable).(*d2components.RenderableMap)
}
// Update will initialize the renderer, start the game loop, and
@ -156,17 +156,17 @@ func (m *RenderSystem) render(screen d2interface.Surface) error {
return errors.New("main viewport not found")
}
sfc, found := m.GetRenderable(id)
renderable, 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)
if renderable.Surface == nil {
renderable.Surface = m.renderer.NewSurface(vp.Width, vp.Height)
}
screen.PushTranslation(vp.Left, vp.Top)
screen.Render(sfc.Surface)
screen.Render(renderable.Surface)
screen.Pop()
}

View File

@ -3,6 +3,8 @@ package d2systems
import (
"path/filepath"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math"
"github.com/gravestench/akara"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
@ -128,7 +130,7 @@ func (s *BaseScene) injectComponentMaps() {
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.Surface).(*d2components.RenderableMap)
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)
@ -240,9 +242,9 @@ func (s *BaseScene) renderViewport(idx int, objects []akara.EID) {
sfc.Surface = s.systems.renderer.NewSurface(camera.Width, camera.Height)
}
cx, cy := int(camera.X())+camera.Width>>1, int(camera.Y())+camera.Height>>1
cx, cy := int(camera.X()), int(camera.Y())
sfc.Surface.PushTranslation(-cx, -cy) // negative because we're offsetting everything that gets rendered
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 {
@ -254,7 +256,7 @@ func (s *BaseScene) renderViewport(idx int, objects []akara.EID) {
}
func (s *BaseScene) renderObject(target d2interface.Surface, id akara.EID) {
sfc, found := s.GetRenderable(id)
renderable, found := s.GetRenderable(id)
if !found {
return
}
@ -269,13 +271,51 @@ func (s *BaseScene) renderObject(target d2interface.Surface, id akara.EID) {
scale = s.AddScale(id)
}
sfc.PushTranslation(int(position.X()), int(position.Y()))
sfc.PushScale(scale.X(), scale.Y())
x, y := int(position.X()), int(position.Y())
target.Render(sfc.Surface)
target.PushTranslation(x, y)
defer target.Pop()
sfc.Pop()
sfc.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
@ -291,3 +331,12 @@ func (s *sceneObjectAssigner) Sprite(x, y float64, imgPath, palPath string) akar
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
}

View File

@ -41,16 +41,16 @@ func (s *MainMenuScene) boot() {
return
}
s.createBackground()
s.createButtons()
s.createTrademarkScreen()
s.createButtons()
s.createBackground()
s.booted = true
}
func (s *MainMenuScene) createBackground() {
s.Info("creating background")
s.Add.Sprite(0, 0, d2resource.GameSelectScreen, d2resource.PaletteSky)
s.Add.SegmentedSprite(0, 0, d2resource.GameSelectScreen, d2resource.PaletteSky, 4, 3, 0)
}
func (s *MainMenuScene) createButtons() {
@ -59,7 +59,7 @@ func (s *MainMenuScene) createButtons() {
func (s *MainMenuScene) createTrademarkScreen() {
s.Info("creating trademark screen")
s.Add.Sprite(0, 0, d2resource.TrademarkScreen, d2resource.PaletteSky)
s.Add.SegmentedSprite(0, 0, d2resource.TrademarkScreen, d2resource.PaletteSky, 4, 3, 0)
}
// Update the main menu scene

View File

@ -18,11 +18,16 @@ const (
func NewSpriteFactorySubsystem(b *akara.BaseSystem, l *d2util.Logger) *SpriteFactory {
spritesToRender := akara.NewFilter().
Require(d2components.Animation). // we want to process entities that have an animation ...
Forbid(d2components.Surface). // ... but are missing a surface
Forbid(d2components.Renderable). // ... but are missing a surface
Build()
spritesToUpdate := akara.NewFilter().
Require(d2components.Animation). // we want to process entities that have an animation ...
Require(d2components.Renderable). // ... but are missing a surface
Build()
sys := &SpriteFactory{
BaseSubscriberSystem: akara.NewBaseSubscriberSystem(spritesToRender),
BaseSubscriberSystem: akara.NewBaseSubscriberSystem(spritesToRender, spritesToUpdate),
Logger: l,
}
@ -52,8 +57,10 @@ type SpriteFactory struct {
*d2components.PaletteMap
*d2components.AnimationMap
*d2components.RenderableMap
*d2components.SegmentedSpriteMap
loadQueue spriteLoadQueue
spritesToRender *akara.Subscription
spritesToUpdate *akara.Subscription
}
// Init the sprite factory, injecting the necessary components
@ -63,6 +70,7 @@ func (t *SpriteFactory) Init(world *akara.World) {
t.loadQueue = make(spriteLoadQueue)
t.spritesToRender = t.Subscriptions[0]
t.spritesToUpdate = t.Subscriptions[1]
t.FilePathMap = t.InjectMap(d2components.FilePath).(*d2components.FilePathMap)
t.PositionMap = t.InjectMap(d2components.Position).(*d2components.PositionMap)
@ -70,12 +78,17 @@ func (t *SpriteFactory) Init(world *akara.World) {
t.DccMap = t.InjectMap(d2components.Dcc).(*d2components.DccMap)
t.PaletteMap = t.InjectMap(d2components.Palette).(*d2components.PaletteMap)
t.AnimationMap = t.InjectMap(d2components.Animation).(*d2components.AnimationMap)
t.RenderableMap = t.InjectMap(d2components.Surface).(*d2components.RenderableMap)
t.RenderableMap = t.InjectMap(d2components.Renderable).(*d2components.RenderableMap)
t.SegmentedSpriteMap = t.InjectMap(d2components.SegmentedSprite).(*d2components.SegmentedSpriteMap)
}
// Update processes the load queue which attempting to create animations, as well as
// binding existing animations to a renderer if one is present.
func (t *SpriteFactory) Update() {
for _, eid := range t.spritesToUpdate.GetEntities() {
t.updateSprite(eid)
}
for _, eid := range t.spritesToRender.GetEntities() {
t.tryRenderingSprite(eid)
}
@ -103,6 +116,19 @@ func (t *SpriteFactory) Sprite(x, y float64, imgPath, palPath string) akara.EID
return spriteID
}
// SegmentedSprite queues a segmented sprite animation to be loaded.
// A segmented sprite is a sprite that has many frames that form the entire sprite.
func (t *SpriteFactory) SegmentedSprite(x, y float64, imgPath, palPath string, xseg, yseg, frame int) akara.EID {
spriteID := t.Sprite(x, y, imgPath, palPath)
s := t.AddSegmentedSprite(spriteID)
s.Xsegments = xseg
s.Ysegments = yseg
s.FrameOffset = frame
return spriteID
}
func (t *SpriteFactory) tryCreatingAnimation(id akara.EID) {
entry := t.loadQueue[id]
imageID, paletteID := entry.spriteImage, entry.spritePalette
@ -172,6 +198,32 @@ func (t *SpriteFactory) tryRenderingSprite(eid akara.EID) {
t.AddRenderable(eid).Surface = sfc
}
func (t *SpriteFactory) updateSprite(eid akara.EID) {
if t.RenderSystem == nil {
return
}
if t.RenderSystem.renderer == nil {
return
}
anim, found := t.GetAnimation(eid)
if !found {
return
}
if anim.Animation == nil {
return
}
renderable, found := t.GetRenderable(eid)
if !found {
return
}
renderable.Surface = anim.GetCurrentFrameSurface()
}
func (t *SpriteFactory) createDc6Animation(
dc6 *d2components.Dc6Component,
pal *d2components.PaletteComponent,