diff --git a/Engine.go b/Engine.go index 6230ee31..7a1ec7d1 100644 --- a/Engine.go +++ b/Engine.go @@ -209,7 +209,7 @@ func (v *Engine) Update() { return } - v.CurrentScene.Update() + v.CurrentScene.Update(float64(1) / ebiten.CurrentTPS()) v.UIManager.Update() } diff --git a/Scenes/Credits.go b/Scenes/Credits.go index c1e0413d..a1ab40bd 100644 --- a/Scenes/Credits.go +++ b/Scenes/Credits.go @@ -1,7 +1,9 @@ package Scenes import ( - "encoding/binary" + "bytes" + "fmt" + "image/color" "strings" "unicode/utf16" "unicode/utf8" @@ -14,37 +16,63 @@ import ( "github.com/hajimehoshi/ebiten" ) +type labelItem struct { + Label *UI.Label + IsHeading bool + Available bool +} + // Credits represents the credits scene type Credits struct { - uiManager *UI.Manager - soundManager *Sound.Manager - fileProvider Common.FileProvider - sceneProvider SceneProvider - creditsBackground *Common.Sprite - exitButton *UI.Button - creditsText []string + uiManager *UI.Manager + soundManager *Sound.Manager + fileProvider Common.FileProvider + sceneProvider SceneProvider + creditsBackground *Common.Sprite + exitButton *UI.Button + creditsText []string + labels []*labelItem + cycleTime float64 + cyclesTillNextLine int + doneWithCredits bool } // CreateCredits creates an instance of the credits scene func CreateCredits(fileProvider Common.FileProvider, sceneProvider SceneProvider, uiManager *UI.Manager, soundManager *Sound.Manager) *Credits { result := &Credits{ - fileProvider: fileProvider, - uiManager: uiManager, - soundManager: soundManager, - sceneProvider: sceneProvider, + fileProvider: fileProvider, + uiManager: uiManager, + soundManager: soundManager, + sceneProvider: sceneProvider, + labels: make([]*labelItem, 0), + cycleTime: 0, + doneWithCredits: false, + cyclesTillNextLine: 0, } return result } -func utf16BytesToString(b []byte, o binary.ByteOrder) string { - utf := make([]uint16, (len(b)+(2-1))/2) - for i := 0; i+(2-1) < len(b); i += 2 { - utf[i/2] = o.Uint16(b[i:]) +func utf16BytesToString(b []byte) (string, error) { + + if len(b)%2 != 0 { + return "", fmt.Errorf("Must have even length byte slice") } - if len(b)/2 < len(utf) { - utf[len(utf)-1] = utf8.RuneError + + u16s := make([]uint16, 1) + + ret := &bytes.Buffer{} + + b8buf := make([]byte, 4) + + lb := len(b) + for i := 0; i < lb; i += 2 { + u16s[0] = uint16(b[i]) + (uint16(b[i+1]) << 8) + r := utf16.Decode(u16s) + n := utf8.EncodeRune(b8buf, r[0]) + ret.Write(b8buf[:n]) } - return string(utf16.Decode(utf)) + + return ret.String(), nil } // Load is called to load the resources for the credits scene @@ -61,7 +89,7 @@ func (v *Credits) Load() []func() { v.uiManager.AddWidget(v.exitButton) }, func() { - fileData := utf16BytesToString(v.fileProvider.LoadFile(ResourcePaths.CreditsText), binary.LittleEndian) + fileData, _ := utf16BytesToString(v.fileProvider.LoadFile(ResourcePaths.CreditsText)) v.creditsText = strings.Split(fileData, "\r\n") }, } @@ -75,11 +103,37 @@ func (v *Credits) Unload() { // Render renders the credits scene func (v *Credits) Render(screen *ebiten.Image) { v.creditsBackground.DrawSegments(screen, 4, 3, 0) + for _, label := range v.labels { + if label.Available { + continue + } + label.Label.Draw(screen) + } } -// Update runs the update logic on the credits scene -func (v *Credits) Update() { +const secondsPerCycle = (float64(4) / float64(100000)) +// Update runs the update logic on the credits scene +func (v *Credits) Update(tickTime float64) { + v.cycleTime += tickTime / float64(1000) + for v.cycleTime >= secondsPerCycle { + v.cycleTime -= secondsPerCycle + v.cyclesTillNextLine-- + if !v.doneWithCredits && v.cyclesTillNextLine <= 0 { + v.addNextItem() + } + + for _, label := range v.labels { + if label.Available { + continue + } + if label.Label.Y-1 < -15 { + label.Available = true + continue + } + label.Label.Y-- + } + } } func (v *Credits) onExitButtonClicked() { @@ -87,3 +141,84 @@ func (v *Credits) onExitButtonClicked() { mainMenu.ShowTrademarkScreen = false v.sceneProvider.SetNextScene(mainMenu) } + +func (v *Credits) addNextItem() { + if len(v.creditsText) == 0 { + v.doneWithCredits = true + return + } + + text := strings.Trim(v.creditsText[0], " ") + v.creditsText = v.creditsText[1:] + if len(text) == 0 { + v.cyclesTillNextLine = 18 + return + } + isHeading := text[0] == '*' + isNextHeading := len(v.creditsText) > 0 && v.creditsText[1][0] == '*' + isNextSpace := len(v.creditsText) > 0 && len(strings.Trim(v.creditsText[1], " ")) == 0 + var label = v.getNewFontLabel(isHeading) + if isHeading { + label.SetText(text[1:]) + } else { + label.SetText(text) + } + + isDoubled := false + if !isHeading && !isNextHeading && !isNextSpace { + isDoubled = true + + // Gotta go side by side + label.MoveTo(390-int(label.Width), 605) + + text2 := strings.Trim(v.creditsText[0], " ") + v.creditsText = v.creditsText[1:] + + isNextHeading = len(v.creditsText[1]) > 0 && v.creditsText[1][0] == '*' + label2 := v.getNewFontLabel(isHeading) + label2.SetText(text2) + + label2.MoveTo(410, 605) + } else { + label.MoveTo(400-int(label.Width/2), 605) + } + + if isHeading && isNextHeading { + v.cyclesTillNextLine = 40 + } else if isNextHeading { + if isDoubled { + v.cyclesTillNextLine = 40 + } else { + v.cyclesTillNextLine = 70 + } + } else if isHeading { + v.cyclesTillNextLine = 40 + } else { + v.cyclesTillNextLine = 18 + } +} + +func (v *Credits) getNewFontLabel(isHeading bool) *UI.Label { + for _, label := range v.labels { + if label.Available { + label.Available = false + return label.Label + } + } + + newLabelItem := &labelItem{ + Available: false, + IsHeading: isHeading, + Label: UI.CreateLabel(v.fileProvider, ResourcePaths.FontFormal10, Palettes.Sky), + } + + if isHeading { + newLabelItem.Label.Color = color.RGBA{255, 88, 82, 255} + } else { + newLabelItem.Label.Color = color.RGBA{198, 178, 150, 255} + + } + + v.labels = append(v.labels, newLabelItem) + return newLabelItem.Label +} diff --git a/Scenes/MainMenu.go b/Scenes/MainMenu.go index 0e96fccb..8e285470 100644 --- a/Scenes/MainMenu.go +++ b/Scenes/MainMenu.go @@ -142,7 +142,7 @@ func (v *MainMenu) Render(screen *ebiten.Image) { } // Update runs the update logic on the main menu -func (v *MainMenu) Update() { +func (v *MainMenu) Update(tickTime float64) { if v.ShowTrademarkScreen { if v.uiManager.CursorButtonPressed(UI.CursorButtonLeft) { v.leftButtonHeld = true diff --git a/Scenes/Scene.go b/Scenes/Scene.go index e33b30fd..4968911b 100644 --- a/Scenes/Scene.go +++ b/Scenes/Scene.go @@ -9,5 +9,5 @@ type Scene interface { Load() []func() Unload() Render(screen *ebiten.Image) - Update() + Update(tickTime float64) } diff --git a/UI/Font.go b/UI/Font.go index 6b678f44..70760f54 100644 --- a/UI/Font.go +++ b/UI/Font.go @@ -58,7 +58,8 @@ func CreateFont(font string, palette Palettes.Palette, fileProvider Common.FileP func (v *Font) GetTextMetrics(text string) (width, height uint32) { width = uint32(0) height = uint32(0) - for _, ch := range text { + for i := 0; i < len(text); i++ { + ch := text[i] metric := v.metrics[uint8(ch)] width += uint32(metric.Width) _, h := v.fontSprite.GetFrameSize(int(ch)) diff --git a/UI/UILabel.go b/UI/Label.go similarity index 100% rename from UI/UILabel.go rename to UI/Label.go