Do not advance state in render (#269)

* Do not advance state in render

* Update advance logic for sprite and region
This commit is contained in:
Alex Yatskov 2019-12-28 20:32:24 -08:00 committed by Tim Sarbin
parent 49b9a190f2
commit c01bedaedf
21 changed files with 162 additions and 91 deletions

View File

@ -1,7 +1,7 @@
package d2asset
import (
"fmt"
"errors"
"path"
"sync"
@ -25,7 +25,7 @@ func createArchiveManager(config *d2corecommon.Configuration) *archiveManager {
return &archiveManager{cache: createCache(ArchiveBudget), config: config}
}
func (am *archiveManager) loadArchiveForFilePath(filePath string) (*d2mpq.MPQ, error) {
func (am *archiveManager) loadArchiveForFile(filePath string) (*d2mpq.MPQ, error) {
am.mutex.Lock()
defer am.mutex.Unlock()
@ -39,7 +39,24 @@ func (am *archiveManager) loadArchiveForFilePath(filePath string) (*d2mpq.MPQ, e
}
}
return nil, fmt.Errorf("file not found: %s", filePath)
return nil, errors.New("file not found")
}
func (am *archiveManager) fileExistsInArchive(filePath string) (bool, error) {
am.mutex.Lock()
defer am.mutex.Unlock()
if err := am.cacheArchiveEntries(); err != nil {
return false, err
}
for _, archiveEntry := range am.entries {
if archiveEntry.hashEntryMap.Contains(filePath) {
return true, nil
}
}
return false, nil
}
func (am *archiveManager) loadArchive(archivePath string) (*d2mpq.MPQ, error) {

View File

@ -2,6 +2,7 @@ package d2asset
import (
"errors"
"log"
"github.com/OpenDiablo2/D2Shared/d2data/d2cof"
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
@ -102,7 +103,20 @@ func LoadFile(filePath string) ([]byte, error) {
return nil, ErrNoInit
}
return singleton.fileManager.loadFile(filePath)
data, err := singleton.fileManager.loadFile(filePath)
if err != nil {
log.Printf("error loading file %s (%v)", filePath, err.Error())
}
return data, err
}
func FileExists(filePath string) (bool, error) {
if singleton == nil {
return false, ErrNoInit
}
return singleton.fileManager.fileExists(filePath)
}
func LoadAnimation(animationPath, palettePath string) (*Animation, error) {

View File

@ -115,7 +115,12 @@ type compositeMode struct {
}
func (c *Composite) createMode(animationMode, weaponClass string, direction int) (*compositeMode, error) {
cof, err := loadCOF(fmt.Sprintf("%s/%s/COF/%s%s%s.COF", c.object.Base, c.object.Token, c.object.Token, animationMode, weaponClass))
cofPath := fmt.Sprintf("%s/%s/COF/%s%s%s.COF", c.object.Base, c.object.Token, c.object.Token, animationMode, weaponClass)
if exists, _ := FileExists(cofPath); !exists {
return nil, errors.New("composite not found")
}
cof, err := loadCOF(cofPath)
if err != nil {
return nil, err
}
@ -249,9 +254,11 @@ func loadCompositeLayer(object *d2datadict.ObjectLookupRecord, layerKey, layerVa
}
for _, animationPath := range animationPaths {
animation, err := LoadAnimationWithTransparency(animationPath, palettePath, transparency)
if err == nil {
return animation, nil
if exists, _ := FileExists(animationPath); exists {
animation, err := LoadAnimationWithTransparency(animationPath, palettePath, transparency)
if err == nil {
return animation, nil
}
}
}

View File

@ -23,7 +23,7 @@ func (fm *fileManager) loadFile(filePath string) ([]byte, error) {
return value.([]byte), nil
}
archive, err := fm.archiveManager.loadArchiveForFilePath(filePath)
archive, err := fm.archiveManager.loadArchiveForFile(filePath)
if err != nil {
return nil, err
}
@ -40,6 +40,11 @@ func (fm *fileManager) loadFile(filePath string) ([]byte, error) {
return data, nil
}
func (fm *fileManager) fileExists(filePath string) (bool, error) {
filePath = fm.fixupFilePath(filePath)
return fm.archiveManager.fileExistsInArchive(filePath)
}
func (fm *fileManager) fixupFilePath(filePath string) string {
filePath = strings.ReplaceAll(filePath, "{LANG}", fm.config.Language)
if strings.ToUpper(d2resource.LanguageCode) == "CHI" {

View File

@ -232,7 +232,7 @@ func (v *CharacterSelect) moveSelectionBox() {
v.d2HeroTitle.SetText(v.gameStates[v.selectedCharacter].HeroName)
}
func (v *CharacterSelect) Update(tickTime float64) {
func (v *CharacterSelect) Advance(tickTime float64) {
if !v.showDeleteConfirmation {
if ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) {
if !v.mouseButtonPressed {

View File

@ -120,7 +120,7 @@ func (v *Credits) Render(screen *d2surface.Surface) {
const secondsPerCycle = float64(0.02)
// Update runs the update logic on the credits scene
func (v *Credits) Update(tickTime float64) {
func (v *Credits) Advance(tickTime float64) {
v.cycleTime += tickTime
for v.cycleTime >= secondsPerCycle {
v.cycleTime -= secondsPerCycle

View File

@ -93,7 +93,7 @@ func (v Game) Render(screen *d2surface.Surface) {
v.gameControls.Render(screen)
}
func (v *Game) Update(tickTime float64) {
func (v *Game) Advance(tickTime float64) {
v.mapEngine.Advance(tickTime)
rx, ry := v.mapEngine.WorldToOrtho(v.hero.AnimatedEntity.LocationX/5, v.hero.AnimatedEntity.LocationY/5)

View File

@ -244,7 +244,12 @@ func (v *MainMenu) Render(screen *d2surface.Surface) {
}
// Update runs the update logic on the main menu
func (v *MainMenu) Update(tickTime float64) {
func (v *MainMenu) Advance(tickTime float64) {
v.diabloLogoLeftBack.Advance(tickTime)
v.diabloLogoRightBack.Advance(tickTime)
v.diabloLogoLeft.Advance(tickTime)
v.diabloLogoRight.Advance(tickTime)
if v.ShowTrademarkScreen {
if v.uiManager.CursorButtonPressed(d2ui.CursorButtonLeft) {
if v.leftButtonHeld {

View File

@ -206,7 +206,7 @@ func (v *MapEngineTest) Render(screen *d2surface.Surface) {
screen.PopN(6)
}
func (v *MapEngineTest) Update(tickTime float64) {
func (v *MapEngineTest) Advance(tickTime float64) {
v.mapEngine.Advance(tickTime)
ctrlPressed := v.uiManager.KeyPressed(ebiten.KeyControl)

View File

@ -35,6 +35,17 @@ type HeroRenderInfo struct {
DeselectSfx *d2audio.SoundEffect
}
func (hri *HeroRenderInfo) Advance(elapsed float64) {
advanceSprite(hri.IdleSprite, elapsed)
advanceSprite(hri.IdleSelectedSprite, elapsed)
advanceSprite(hri.ForwardWalkSprite, elapsed)
advanceSprite(hri.ForwardWalkSpriteOverlay, elapsed)
advanceSprite(hri.SelectedSprite, elapsed)
advanceSprite(hri.SelectedSpriteOverlay, elapsed)
advanceSprite(hri.BackWalkSprite, elapsed)
advanceSprite(hri.BackWalkSpriteOverlay, elapsed)
}
type SelectHeroClass struct {
uiManager *d2ui.Manager
soundManager *d2audio.Manager
@ -471,12 +482,12 @@ func (v *SelectHeroClass) Render(screen *d2surface.Surface) {
}
}
func (v *SelectHeroClass) Update(tickTime float64) {
func (v *SelectHeroClass) Advance(tickTime float64) {
canSelect := true
for _, info := range v.heroRenderInfo {
info.Advance(tickTime)
if info.Stance != d2enum.HeroStanceIdle && info.Stance != d2enum.HeroStanceIdleSelected && info.Stance != d2enum.HeroStanceSelected {
canSelect = false
break
}
}
allIdle := true
@ -652,3 +663,9 @@ func drawSprite(sprite *d2render.Sprite, target *d2surface.Surface) {
sprite.Render(target)
}
}
func advanceSprite(sprite *d2render.Sprite, elapsed float64) {
if sprite != nil {
sprite.Advance(elapsed)
}
}

View File

@ -52,7 +52,10 @@ type Engine struct {
// CreateEngine creates and instance of the OpenDiablo2 engine
func CreateEngine() *Engine {
result := &Engine{timeScale: 1.0}
result := &Engine{
lastTime: d2helper.Now(),
timeScale: 1.0,
}
result.Settings = d2corecommon.LoadConfiguration()
if err := result.Settings.Save(); err != nil {
@ -137,8 +140,8 @@ func (v *Engine) updateScene() {
v.ResetLoading()
}
// Update updates the internal state of the engine
func (v *Engine) Update() {
// Advance updates the internal state of the engine
func (v *Engine) Advance() {
if ebiten.IsKeyPressed(ebiten.KeyAlt) && ebiten.IsKeyPressed(ebiten.KeyEnter) {
if !v.fullscreenKey {
ebiten.SetFullscreen(!ebiten.IsFullscreen())
@ -169,13 +172,13 @@ func (v *Engine) Update() {
deltaTime := (currentTime - v.lastTime) * v.timeScale
v.lastTime = currentTime
v.CurrentScene.Update(deltaTime)
v.UIManager.Update()
v.CurrentScene.Advance(deltaTime)
v.UIManager.Advance(deltaTime)
d2term.Advance(deltaTime)
}
// Draw draws the game
func (v Engine) Draw(target *d2surface.Surface) {
func (v Engine) Render(target *d2surface.Surface) {
if v.loadingProgress < 1.0 {
v.LoadingSprite.SetCurrentFrame(int(d2helper.Max(0, d2helper.Min(uint32(v.LoadingSprite.GetFrameCount()-1), uint32(float64(v.LoadingSprite.GetFrameCount()-1)*v.loadingProgress)))))
v.LoadingSprite.Render(target)

View File

@ -5,6 +5,7 @@ import "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface"
// Drawable represents an instance that can be drawn
type Drawable interface {
Render(target *d2surface.Surface)
Advance(elapsed float64)
GetSize() (width, height int)
SetPosition(x, y int)
GetPosition() (x, y int)

View File

@ -7,5 +7,5 @@ type Scene interface {
Load() []func()
Unload()
Render(target *d2surface.Surface)
Update(tickTime float64)
Advance(tickTime float64)
}

View File

@ -34,7 +34,7 @@ type MapRegion struct {
startY float64
imageCacheRecords map[uint32]*ebiten.Image
seed int64
currentFrame byte
currentFrame int
lastFrameTime float64
}
@ -154,18 +154,6 @@ func (mr *MapRegion) loadEntities() []MapEntity {
return entities
}
func (mr *MapRegion) updateAnimations() {
now := d2helper.Now()
framesToAdd := math.Floor((now - mr.lastFrameTime) / 0.1)
if framesToAdd > 0 {
mr.lastFrameTime += 0.1 * framesToAdd
mr.currentFrame += byte(math.Floor(framesToAdd))
if mr.currentFrame > 9 {
mr.currentFrame = 0
}
}
}
func (mr *MapRegion) getStartTilePosition() (float64, float64) {
return float64(mr.tileRect.Left) + mr.startX, float64(mr.tileRect.Top) + mr.startY
}
@ -224,8 +212,17 @@ func (mr *MapRegion) isVisbile(viewport *Viewport) bool {
return viewport.IsTileRectVisible(mr.tileRect)
}
func (mr *MapRegion) advance(tickTime float64) {
mr.updateAnimations()
func (mr *MapRegion) advance(elapsed float64) {
frameLength := 0.1
mr.lastFrameTime += elapsed
framesAdvanced := int(mr.lastFrameTime / frameLength)
mr.lastFrameTime -= float64(framesAdvanced) * frameLength
mr.currentFrame += framesAdvanced
if mr.currentFrame > 9 {
mr.currentFrame = 0
}
}
func (mr *MapRegion) getTileWorldPosition(tileX, tileY int) (float64, float64) {
@ -322,7 +319,7 @@ func (mr *MapRegion) renderFloor(tile d2ds1.FloorShadowRecord, viewport *Viewpor
if !tile.Animated {
img = mr.getImageCacheRecord(tile.Style, tile.Sequence, 0, tile.RandomIndex)
} else {
img = mr.getImageCacheRecord(tile.Style, tile.Sequence, 0, mr.currentFrame)
img = mr.getImageCacheRecord(tile.Style, tile.Sequence, 0, byte(mr.currentFrame))
}
if img == nil {
log.Printf("Render called on uncached floor {%v,%v}", tile.Style, tile.Sequence)

View File

@ -178,7 +178,7 @@ func (v *Button) Activate() {
}
// Render renders the button
func (v Button) Render(target *d2surface.Surface) {
func (v *Button) Render(target *d2surface.Surface) {
target.PushCompositeMode(ebiten.CompositeModeSourceAtop)
target.PushFilter(ebiten.FilterNearest)
target.PushTranslation(v.x, v.y)
@ -199,8 +199,12 @@ func (v Button) Render(target *d2surface.Surface) {
}
}
func (v *Button) Advance(elapsed float64) {
}
// GetEnabled returns the enabled state
func (v Button) GetEnabled() bool {
func (v *Button) GetEnabled() bool {
return v.enabled
}
@ -210,7 +214,7 @@ func (v *Button) SetEnabled(enabled bool) {
}
// GetSize returns the size of the button
func (v Button) GetSize() (int, int) {
func (v *Button) GetSize() (int, int) {
return v.width, v.height
}
@ -221,12 +225,12 @@ func (v *Button) SetPosition(x, y int) {
}
// GetPosition returns the location of the button
func (v Button) GetPosition() (x, y int) {
func (v *Button) GetPosition() (x, y int) {
return v.x, v.y
}
// GetVisible returns the visibility of the button
func (v Button) GetVisible() bool {
func (v *Button) GetVisible() bool {
return v.visible
}
@ -236,7 +240,7 @@ func (v *Button) SetVisible(visible bool) {
}
// GetPressed returns the pressed state of the button
func (v Button) GetPressed() bool {
func (v *Button) GetPressed() bool {
return v.pressed
}

View File

@ -40,7 +40,7 @@ func CreateCheckbox(checkState bool) Checkbox {
return result
}
func (v Checkbox) Render(target *d2surface.Surface) {
func (v *Checkbox) Render(target *d2surface.Surface) {
target.PushCompositeMode(ebiten.CompositeModeSourceAtop)
target.PushTranslation(v.x, v.y)
target.PushFilter(ebiten.FilterNearest)
@ -52,7 +52,12 @@ func (v Checkbox) Render(target *d2surface.Surface) {
target.Render(v.Image)
}
}
func (v Checkbox) GetEnabled() bool {
func (v *Checkbox) Advance(elapsed float64) {
}
func (v *Checkbox) GetEnabled() bool {
return v.enabled
}
@ -60,18 +65,18 @@ func (v *Checkbox) SetEnabled(enabled bool) {
v.enabled = enabled
}
func (v Checkbox) SetPressed(pressed bool) {
func (v *Checkbox) SetPressed(pressed bool) {
}
func (v *Checkbox) SetCheckState(checkState bool) {
v.checkState = checkState
}
func (v Checkbox) GetCheckState() bool {
func (v *Checkbox) GetCheckState() bool {
return v.checkState
}
func (v Checkbox) GetPressed() bool {
func (v *Checkbox) GetPressed() bool {
return v.checkState
}
@ -87,15 +92,15 @@ func (v *Checkbox) Activate() {
v.onClick()
}
func (v Checkbox) GetPosition() (int, int) {
func (v *Checkbox) GetPosition() (int, int) {
return v.x, v.y
}
func (v Checkbox) GetSize() (int, int) {
func (v *Checkbox) GetSize() (int, int) {
return v.width, v.height
}
func (v Checkbox) GetVisible() bool {
func (v *Checkbox) GetVisible() bool {
return v.visible
}

View File

@ -72,7 +72,13 @@ func (v *Manager) Render(target *d2surface.Surface) {
}
// Update updates all of the UI elements
func (v *Manager) Update() {
func (v *Manager) Advance(elapsed float64) {
for _, widget := range v.widgets {
if widget.GetVisible() {
widget.Advance(elapsed)
}
}
v.cursorButtons = 0
if ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) {
if !v.waitForLeftMouseUp {

View File

@ -69,7 +69,7 @@ func (v *Scrollbar) Activate() {
}
}
func (v Scrollbar) GetLastDirChange() int {
func (v *Scrollbar) GetLastDirChange() int {
return v.lastDirChange
}
@ -96,6 +96,10 @@ func (v *Scrollbar) Render(target *d2surface.Surface) {
v.scrollbarSprite.RenderSegmented(target, 1, 1, 4+offset)
}
func (v *Scrollbar) Advance(elapsed float64) {
v.scrollbarSprite.Advance(elapsed)
}
func (v *Scrollbar) GetSize() (width, height int) {
return 10, int(v.height)
}
@ -134,10 +138,10 @@ func (v *Scrollbar) SetCurrentOffset(currentOffset int) {
v.currentOffset = currentOffset
}
func (v Scrollbar) GetMaxOffset() int {
func (v *Scrollbar) GetMaxOffset() int {
return v.maxOffset
}
func (v Scrollbar) GetCurrentOffset() int {
func (v *Scrollbar) GetCurrentOffset() int {
return v.currentOffset
}

View File

@ -52,7 +52,7 @@ func repeatingKeyPressed(key ebiten.Key) bool {
return false
}
func (v TextBox) Render(target *d2surface.Surface) {
func (v *TextBox) Render(target *d2surface.Surface) {
if !v.visible {
return
}
@ -63,6 +63,10 @@ func (v TextBox) Render(target *d2surface.Surface) {
}
}
func (v *TextBox) Advance(elapsed float64) {
}
func (v *TextBox) Update() {
if !v.visible || !v.enabled {
return
@ -80,7 +84,7 @@ func (v *TextBox) Update() {
}
}
func (v TextBox) GetText() string {
func (v *TextBox) GetText() string {
return v.text
}
@ -108,7 +112,7 @@ func (v *TextBox) SetText(newText string) {
}
}
func (v TextBox) GetSize() (width, height int) {
func (v *TextBox) GetSize() (width, height int) {
return v.bgSprite.GetCurrentFrameSize()
}
@ -120,11 +124,11 @@ func (v *TextBox) SetPosition(x, y int) {
v.bgSprite.SetPosition(v.x, v.y+26)
}
func (v TextBox) GetPosition() (x, y int) {
func (v *TextBox) GetPosition() (x, y int) {
return v.x, v.y
}
func (v TextBox) GetVisible() bool {
func (v *TextBox) GetVisible() bool {
return v.visible
}
@ -132,7 +136,7 @@ func (v *TextBox) SetVisible(visible bool) {
v.visible = visible
}
func (v TextBox) GetEnabled() bool {
func (v *TextBox) GetEnabled() bool {
return v.enabled
}
@ -144,7 +148,7 @@ func (v *TextBox) SetPressed(pressed bool) {
// no op
}
func (v TextBox) GetPressed() bool {
func (v *TextBox) GetPressed() bool {
return false
}
@ -152,6 +156,6 @@ func (v *TextBox) OnActivated(callback func()) {
// no op
}
func (v TextBox) Activate() {
func (v *TextBox) Activate() {
//no op
}

View File

@ -9,10 +9,9 @@ import (
)
type Sprite struct {
x int
y int
lastFrameTime float64
animation *d2asset.Animation
x int
y int
animation *d2asset.Animation
}
func LoadSprite(animationPath, palettePath string) (*Sprite, error) {
@ -21,7 +20,7 @@ func LoadSprite(animationPath, palettePath string) (*Sprite, error) {
return nil, err
}
return &Sprite{lastFrameTime: d2helper.Now(), animation: animation}, nil
return &Sprite{animation: animation}, nil
}
func MustLoadSprite(animationPath, palettePath string) *Sprite {
@ -34,10 +33,6 @@ func MustLoadSprite(animationPath, palettePath string) *Sprite {
}
func (s *Sprite) Render(target *d2surface.Surface) error {
if err := s.advance(); err != nil {
return err
}
_, frameHeight := s.animation.GetCurrentFrameSize()
target.PushTranslation(s.x, s.y-frameHeight)
@ -46,10 +41,6 @@ func (s *Sprite) Render(target *d2surface.Surface) error {
}
func (s *Sprite) RenderSegmented(target *d2surface.Surface, segmentsX, segmentsY, frameOffset int) error {
if err := s.advance(); err != nil {
return err
}
var currentY int
for y := 0; y < segmentsY; y++ {
var currentX int
@ -127,22 +118,18 @@ func (s *Sprite) GetDirection() int {
}
func (s *Sprite) SetCurrentFrame(frameIndex int) error {
s.lastFrameTime = d2helper.Now()
return s.animation.SetCurrentFrame(frameIndex)
}
func (s *Sprite) Rewind() {
s.lastFrameTime = d2helper.Now()
s.animation.SetCurrentFrame(0)
}
func (s *Sprite) PlayForward() {
s.lastFrameTime = d2helper.Now()
s.animation.PlayForward()
}
func (s *Sprite) PlayBackward() {
s.lastFrameTime = d2helper.Now()
s.animation.PlayBackward()
}
@ -170,11 +157,6 @@ func (s *Sprite) SetBlend(blend bool) {
s.animation.SetBlend(blend)
}
func (s *Sprite) advance() error {
lastFrameTime := d2helper.Now()
if err := s.animation.Advance(lastFrameTime - s.lastFrameTime); err != nil {
return err
}
s.lastFrameTime = lastFrameTime
return nil
func (s *Sprite) Advance(elapsed float64) error {
return s.animation.Advance(elapsed)
}

View File

@ -67,10 +67,10 @@ func main() {
}
func update(screen *ebiten.Image) error {
d2Engine.Update()
d2Engine.Advance()
if !ebiten.IsDrawingSkipped() {
surface := d2surface.CreateSurface(screen)
d2Engine.Draw(surface)
d2Engine.Render(surface)
if surface.GetDepth() > 0 {
panic("detected surface stack leak")
}