From 05dae775e48bdfe8118109abd899aeb3b263ca3a Mon Sep 17 00:00:00 2001 From: gravestench Date: Sun, 22 Nov 2020 16:34:46 -0800 Subject: [PATCH] scene rendering now works * scene systems now render their objects to the main viewport * added SegmentedSprite component * added SegmentedSprite to sprite factory --- d2core/d2components/all_component_ids.go | 1 + d2core/d2components/renderable.go | 6 +-- d2core/d2components/segmented_sprite.go | 67 ++++++++++++++++++++++++ d2core/d2systems/render.go | 12 ++--- d2core/d2systems/scene_base.go | 67 ++++++++++++++++++++---- d2core/d2systems/scene_main_menu.go | 8 +-- d2core/d2systems/sprite_factory.go | 58 ++++++++++++++++++-- 7 files changed, 194 insertions(+), 25 deletions(-) create mode 100644 d2core/d2components/segmented_sprite.go diff --git a/d2core/d2components/all_component_ids.go b/d2core/d2components/all_component_ids.go index e230de0f..832f00c9 100644 --- a/d2core/d2components/all_component_ids.go +++ b/d2core/d2components/all_component_ids.go @@ -36,4 +36,5 @@ const ( SizeCID AnimationCID ScaleCID + SegmentedSpriteCID ) diff --git a/d2core/d2components/renderable.go b/d2core/d2components/renderable.go index 82b6889b..1ade8d30 100644 --- a/d2core/d2components/renderable.go +++ b/d2core/d2components/renderable.go @@ -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{ diff --git a/d2core/d2components/segmented_sprite.go b/d2core/d2components/segmented_sprite.go new file mode 100644 index 00000000..7262b6dd --- /dev/null +++ b/d2core/d2components/segmented_sprite.go @@ -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 +} diff --git a/d2core/d2systems/render.go b/d2core/d2systems/render.go index 58ee2335..029b4063 100644 --- a/d2core/d2systems/render.go +++ b/d2core/d2systems/render.go @@ -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() } diff --git a/d2core/d2systems/scene_base.go b/d2core/d2systems/scene_base.go index 2f93bdb3..46214a39 100644 --- a/d2core/d2systems/scene_base.go +++ b/d2core/d2systems/scene_base.go @@ -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 +} diff --git a/d2core/d2systems/scene_main_menu.go b/d2core/d2systems/scene_main_menu.go index e70d520d..f2c93073 100644 --- a/d2core/d2systems/scene_main_menu.go +++ b/d2core/d2systems/scene_main_menu.go @@ -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 diff --git a/d2core/d2systems/sprite_factory.go b/d2core/d2systems/sprite_factory.go index 37c11568..5ecf67d1 100644 --- a/d2core/d2systems/sprite_factory.go +++ b/d2core/d2systems/sprite_factory.go @@ -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,