Compare commits

...

2 Commits

9 changed files with 580 additions and 125 deletions

View File

@ -10,7 +10,7 @@
- [ ] Add more unit tests?
- [X] Fix z-fighting of monsters.
- [X] Add exploding animation of monsters.
- [ ] Add audio settings (music & sound volume).
- [X] Add audio settings (music & sound volume).
- [X] Hearts must be saved as well for resume.
- [ ] Add demo mode.
- [ ] Add touch controls

View File

@ -41,6 +41,9 @@ func newAppContext(ctx ui.Context, settings *tins2021.Settings, score *tins2021.
DyingMonsterTextures: map[tins2021.MonsterType]tins2021.AnimatedTexture{},
}
app.Audio.SampleVolume = settings.Audio.SoundVolume
app.Audio.MusicVolume = settings.Audio.MusicVolume
monsterNames := map[tins2021.MonsterType]string{
tins2021.MonsterTypeStraight: "straight-walking",
tins2021.MonsterTypeRandom: "random-walking",
@ -83,6 +86,33 @@ func (app *appContext) PlayNext(ctx ui.Context, controller *levelController) {
controller.Play(level)
}
func (app *appContext) playNextGameMusic(ctx ui.Context) {
if app.GameMusic != nil {
return
}
const songs = 4
pick := func() int {
for {
s := rand.Intn(songs) + 1
if s == app.GameMusicSong {
continue
}
app.GameMusicSong = s
return s
}
}
song := fmt.Sprintf("song_game_%d.mp3", pick())
app.GameMusic, _ = app.Audio.PlayMusic(song, func(m *Music) {
m.OnFinished = func() {
if app.GameMusic == nil {
return
}
app.GameMusic = nil
app.playNextGameMusic(ctx)
}
})
}
func (app *appContext) PlayResume(ctx ui.Context) {
level := tins2021.NewLevel()
level.Score = app.Score.Current.Score
@ -131,31 +161,22 @@ func (app *appContext) ShowMainMenu(ctx ui.Context) {
app.show(ctx, newMainMenu(app, ctx))
}
func (app *appContext) playNextGameMusic(ctx ui.Context) {
if app.GameMusic != nil {
return
func (app *appContext) setMusicVolume(volume float64) {
app.Settings.Audio.MusicVolume = volume
app.Audio.MusicVolume = volume
menu := app.MenuMusic
if menu != nil {
menu.Volume.Volume = volume
}
const songs = 4
pick := func() int {
for {
s := rand.Intn(songs) + 1
if s == app.GameMusicSong {
continue
}
app.GameMusicSong = s
return s
}
game := app.GameMusic
if game != nil {
game.Volume.Volume = volume
}
song := fmt.Sprintf("song_game_%d.mp3", pick())
app.GameMusic, _ = app.Audio.PlayMusic(song, func(m *Music) {
m.OnFinished = func() {
if app.GameMusic == nil {
return
}
app.GameMusic = nil
app.playNextGameMusic(ctx)
}
})
}
func (app *appContext) setSoundVolume(volume float64) {
app.Settings.Audio.SoundVolume = volume
app.Audio.SampleVolume = volume
}
func (app *appContext) switchToPlayMusic(ctx ui.Context) {

View File

@ -35,8 +35,8 @@ func NewAudioPlayer(resources ui.Resources, prefix string) *AudioPlayer {
prefix: prefix,
SampleRate: rate,
Samples: map[string]Sample{},
SampleVolume: 1,
MusicVolume: 1,
SampleVolume: 0,
MusicVolume: 0,
}
}
@ -83,8 +83,8 @@ func (p *AudioPlayer) PlaySample(name string) error {
speaker.Play(&effects.Volume{
Streamer: p.resample(sample.Stream(), sample.SampleRate),
Base: 2,
Volume: float64(sample.Volume),
Silent: false,
Volume: p.SampleVolume - sample.Volume,
Silent: p.SampleVolume == minVolume,
})
return nil
}
@ -106,8 +106,8 @@ func (p *AudioPlayer) PlayMusic(name string, init func(*Music)) (*Music, error)
Volume: &effects.Volume{
Streamer: p.resample(closer, format.SampleRate),
Base: 2,
Volume: 1,
Silent: false,
Volume: p.MusicVolume,
Silent: p.MusicVolume == minVolume,
},
}
if init != nil {

View File

@ -44,7 +44,7 @@ func newMainMenu(app *appContext, ctx ui.Context) ui.Control {
menu.Add("Resume", func(ctx ui.Context) { app.PlayResume(ctx) })
}
menu.Add("Highscores", func(ctx ui.Context) { app.ShowHighscores(ctx) })
menu.Add("Controls", func(ctx ui.Context) { app.ShowSettings(ctx) })
menu.Add("Settings", func(ctx ui.Context) { app.ShowSettings(ctx) })
menu.Add("Credits", func(ctx ui.Context) { app.ShowCredits(ctx) })
menu.Add("Quit", func(ctx ui.Context) { ctx.Quit() })

View File

@ -6,6 +6,7 @@ import (
"image/color"
"github.com/golang/freetype/truetype"
"github.com/llgcode/draw2d"
"github.com/llgcode/draw2d/draw2dimg"
"opslag.de/schobers/geom"
"opslag.de/schobers/tins2021"
@ -13,6 +14,12 @@ import (
"opslag.de/schobers/zntg/ui"
)
const (
controlsTypeWASD = "wasd"
controlsTypeArrows = "arrows"
controlsTypeCustom = "custom"
)
const keyboardKeyCornerRadius = .1 * keyboardKeyWidth
const keyboardKeyHeight = .2 * keyboardLayoutTextureWidth
const keyboardKeySkew = .15
@ -20,6 +27,14 @@ const keyboardKeyWidth = .25 * keyboardLayoutTextureWidth
const keyboardLayoutTextureHeight = 256
const keyboardLayoutTextureWidth = 2 * keyboardLayoutTextureHeight
const maxVolume = 2
const minVolume = -8
const volumeControlBarWidth = 154
const volumeControlKnobWidth = 154
const volumeControlTextureHeight = 256
const volumeControlTextureWidth = 2*volumeControlKnobWidth + 2*volumeControlBarWidth
func drawKey(ctx *draw2dimg.GraphicContext, font *truetype.Font, center geom.PointF, key rune, color color.Color) {
const cornerRadius = keyboardKeyCornerRadius
const keyHeight_5 = .5 * keyboardKeyHeight
@ -115,6 +130,68 @@ func generateKeys(resources ui.Resources, keys ...keyboardLayoutKey) image.Image
return im
}
func generateVolumeControlTexture() (image.Image, map[string]geom.RectangleF) {
im := image.NewRGBA(image.Rect(0, 0, volumeControlTextureWidth, volumeControlTextureHeight))
ctx := draw2dimg.NewGraphicContext(im)
const unitMultiplier = float64(volumeControlTextureHeight)
var left float64
coord := func(x, y float64) (float64, float64) {
return left + x*unitMultiplier, y * unitMultiplier
}
regions := map[string]geom.RectangleF{}
ctx.SetFillColor(color.White)
ctx.SetStrokeColor(color.White)
ctx.SetLineCap(draw2d.SquareCap)
ctx.SetLineWidth(16)
ctx.MoveTo(coord(.1, .5))
ctx.LineTo(coord(.4, .2))
ctx.LineTo(coord(.52, .32))
ctx.LineTo(coord(.52, .68))
ctx.LineTo(coord(.4, .8))
ctx.Close()
ctx.Stroke()
regions["leftKnob"] = geom.RectF(0, 0, volumeControlKnobWidth, volumeControlTextureHeight)
left += volumeControlKnobWidth
ctx.SetLineWidth(16)
ctx.MoveTo(coord(.1, .2))
ctx.LineTo(coord(.3, .0))
ctx.LineTo(coord(.5, .2))
ctx.LineTo(coord(.5, .8))
ctx.LineTo(coord(.3, 1))
ctx.LineTo(coord(.1, .8))
ctx.Close()
ctx.Stroke()
regions["bar"] = geom.RectF(left, 0, left+volumeControlBarWidth, volumeControlTextureHeight)
left += volumeControlBarWidth
ctx.MoveTo(coord(.1, .2))
ctx.LineTo(coord(.3, .0))
ctx.LineTo(coord(.5, .2))
ctx.LineTo(coord(.5, .8))
ctx.LineTo(coord(.3, 1))
ctx.LineTo(coord(.1, .8))
ctx.Close()
ctx.FillStroke()
regions["barFilled"] = geom.RectF(left, 0, left+volumeControlBarWidth, volumeControlTextureHeight)
left += volumeControlBarWidth
ctx.SetLineWidth(16)
ctx.MoveTo(coord(.5, .5))
ctx.LineTo(coord(.2, .2))
ctx.LineTo(coord(.08, .32))
ctx.LineTo(coord(.08, .68))
ctx.LineTo(coord(.2, .8))
ctx.Close()
ctx.Stroke()
regions["rightKnob"] = geom.RectF(left, 0, left+volumeControlKnobWidth, volumeControlTextureHeight)
return im, regions
}
func generateWASDKeys(resources ui.Resources) image.Image {
return generateKeys(resources,
keyboardLayoutKey{Position: geom.PtF(.45, .25), Key: 'W'},
@ -130,24 +207,19 @@ type keyboardLayoutKey struct {
Highlight bool
}
type settings struct {
ui.StackPanel
type keyboardLayoutSettings struct {
ui.ControlBase
app *appContext
Active bool
ActiveLayout int
SelectedLayout int
SelectingCustom int
}
const (
controlsTypeWASD = "wasd"
controlsTypeArrows = "arrows"
controlsTypeCustom = "custom"
)
func newSettings(app *appContext, ctx ui.Context) *settings {
func newKeyboardLayoutSettings(app *appContext, ctx ui.Context) *keyboardLayoutSettings {
ctx.Textures().CreateTextureGo("layout-wasd", generateWASDKeys(ctx.Resources()), true)
ctx.Textures().CreateTextureGo("layout-arrows", generateArrowKeys(ctx.Resources()), true)
ctx.Textures().CreateTextureGo("layout-select-1", generateArrowKeysHighlight(ctx.Resources(), [4]bool{true, false, false, false}), true)
@ -163,42 +235,19 @@ func newSettings(app *appContext, ctx ui.Context) *settings {
layout = 2
}
settings := &settings{app: app, ActiveLayout: layout, SelectedLayout: layout}
settings := &keyboardLayoutSettings{app: app, ActiveLayout: layout, SelectedLayout: layout}
settings.renderCustomLayout(ctx)
return settings
}
var supportedCustomKeys = map[ui.Key]string{
ui.KeyA: "A",
ui.KeyB: "B",
ui.KeyC: "C",
ui.KeyD: "D",
ui.KeyE: "E",
ui.KeyF: "F",
ui.KeyG: "G",
ui.KeyH: "H",
ui.KeyI: "I",
ui.KeyJ: "J",
ui.KeyK: "K",
ui.KeyL: "L",
ui.KeyM: "M",
ui.KeyN: "N",
ui.KeyO: "O",
ui.KeyP: "P",
ui.KeyQ: "Q",
ui.KeyR: "R",
ui.KeyS: "S",
ui.KeyT: "T",
ui.KeyU: "U",
ui.KeyV: "V",
ui.KeyW: "W",
ui.KeyX: "X",
ui.KeyY: "Y",
ui.KeyZ: "Z",
func (s *keyboardLayoutSettings) DesiredSize(ctx ui.Context, size geom.PointF32) geom.PointF32 {
scale := tins2021.FindScaleRound(keyboardLayoutTextureWidth, .28*size.X)
font := ctx.Fonts().Font("default")
return geom.PtF32(geom.NaN32(), 2*font.Height()+scale*keyboardLayoutTextureHeight)
}
func (s *settings) Handle(ctx ui.Context, e ui.Event) bool {
if s.StackPanel.Handle(ctx, e) {
func (s *keyboardLayoutSettings) Handle(ctx ui.Context, e ui.Event) bool {
if s.ControlBase.Handle(ctx, e) {
return true
}
switch e := e.(type) {
@ -241,11 +290,17 @@ func (s *settings) Handle(ctx ui.Context, e ui.Event) bool {
s.app.MenuInteraction()
return true
case ui.KeyLeft:
s.setActiveLayout(s.ActiveLayout - 1)
if s.Active {
s.setActiveLayout(s.ActiveLayout - 1)
}
case ui.KeyRight:
s.setActiveLayout(s.ActiveLayout + 1)
if s.Active {
s.setActiveLayout(s.ActiveLayout + 1)
}
case ui.KeyEnter:
s.selectLayout()
if s.Active {
s.selectLayout()
}
}
case *ui.MouseMoveEvent:
if s.SelectingCustom == 0 {
@ -254,6 +309,7 @@ func (s *settings) Handle(ctx ui.Context, e ui.Event) bool {
s.setActiveLayout(layout)
}
}
s.Active = s.IsOver()
case *ui.MouseButtonDownEvent:
if s.SelectingCustom == 0 {
if e.Button == ui.MouseButtonLeft {
@ -268,34 +324,20 @@ func (s *settings) Handle(ctx ui.Context, e ui.Event) bool {
return false
}
func (s *settings) selectLayout() {
s.app.MenuInteraction()
switch s.ActiveLayout {
case 0:
s.SelectedLayout = 0
s.app.Settings.Controls.Type = controlsTypeWASD
case 1:
s.SelectedLayout = 1
s.app.Settings.Controls.Type = controlsTypeArrows
case 2:
s.SelectingCustom = 1
}
}
func (s *settings) isOverLayout(ctx ui.Context, mouse geom.PointF32) int {
func (s *keyboardLayoutSettings) isOverLayout(ctx ui.Context, mouse geom.PointF32) int {
bounds := s.Bounds()
center := bounds.Center()
width := bounds.Dx()
scale := tins2021.FindScaleRound(keyboardLayoutTextureWidth, .28*width)
font := ctx.Fonts().Font("default")
top := bounds.Min.Y
bottom := top + 2*font.Height() + scale*keyboardLayoutTextureHeight
for i := 0; i < 3; i++ {
left := (.04 + .32*float32(i)) * width
right := left + .28*width
top := center.Y - 2*font.Height()
bottom := center.Y + scale*keyboardLayoutTextureWidth
if mouse.In(geom.RectF32(left, top, right, bottom)) {
return i
}
@ -303,23 +345,37 @@ func (s *settings) isOverLayout(ctx ui.Context, mouse geom.PointF32) int {
return -1
}
func (s *settings) setActiveLayout(layout int) {
layout = (layout + 3) % 3
change := layout != s.ActiveLayout
s.ActiveLayout = (layout + 3) % 3
if change {
s.app.MenuInteraction()
}
}
func (s *settings) Render(ctx ui.Context) {
func (s *keyboardLayoutSettings) PostRender(ctx ui.Context) {
bounds := s.Bounds()
center := bounds.Center()
width := bounds.Dx()
renderer := ctx.Renderer()
scale := tins2021.FindScaleRound(keyboardLayoutTextureWidth, .28*width)
font := ctx.Fonts().Font("default")
normalColor := ctx.Style().Palette.Text
top := bounds.Min.Y
layoutTop := top + 2*font.Height()
if s.SelectingCustom > 0 {
renderer.FillRectangle(geom.ZeroPtF32.RectRel(renderer.Size().ToF32()), zntg.MustHexColor(`#000000DF`))
selectTexture := fmt.Sprintf("layout-select-%d", s.SelectingCustom)
layoutLeft := .36 * width
layoutCenter := layoutLeft + .14*width
renderer.TextAlign(font, geom.PtF32(layoutCenter, top), normalColor, "PRESS KEY TO ASSIGN", ui.AlignCenter)
renderer.DrawTexturePoint(ctx.Textures().ScaledByName(selectTexture, scale), geom.PtF32(layoutLeft, layoutTop))
}
}
func (s *keyboardLayoutSettings) Render(ctx ui.Context) {
bounds := s.Bounds()
width := bounds.Dx()
renderer := ctx.Renderer()
scale := tins2021.FindScaleRound(keyboardLayoutTextureWidth, .28*width)
font := ctx.Fonts().Font("default")
layouts := []string{
@ -332,6 +388,8 @@ func (s *settings) Render(ctx ui.Context) {
normalColor := ctx.Style().Palette.Text
highlightColor := ctx.Style().Palette.Primary
top := bounds.Min.Y
layoutTop := top + 2*font.Height()
for i, layout := range layouts {
layoutLeft := (.04 + .32*float32(i)) * width
layoutCenter := layoutLeft + .14*width
@ -345,23 +403,12 @@ func (s *settings) Render(ctx ui.Context) {
layoutColor = highlightColor
}
renderer.TextAlign(font, geom.PtF32(layoutCenter, center.Y-2*font.Height()), textColor, layout, ui.AlignCenter)
renderer.DrawTexturePointOptions(ctx.Textures().ScaledByName(layoutTextures[i], scale), geom.PtF32(layoutLeft, center.Y), ui.DrawOptions{Tint: layoutColor})
}
if s.SelectingCustom > 0 {
renderer.FillRectangle(bounds, zntg.MustHexColor(`#000000DF`))
selectTexture := fmt.Sprintf("layout-select-%d", s.SelectingCustom)
layoutLeft := .36 * width
layoutCenter := layoutLeft + .14*width
renderer.TextAlign(font, geom.PtF32(layoutCenter, center.Y-2*font.Height()), normalColor, "PRESS KEY TO ASSIGN", ui.AlignCenter)
renderer.DrawTexturePoint(ctx.Textures().ScaledByName(selectTexture, scale), geom.PtF32(layoutLeft, center.Y))
renderer.TextAlign(font, geom.PtF32(layoutCenter, top), textColor, layout, ui.AlignCenter)
renderer.DrawTexturePointOptions(ctx.Textures().ScaledByName(layoutTextures[i], scale), geom.PtF32(layoutLeft, layoutTop), ui.DrawOptions{Tint: layoutColor})
}
}
func (s *settings) renderCustomLayout(ctx ui.Context) {
func (s *keyboardLayoutSettings) renderCustomLayout(ctx ui.Context) {
runeOrQuestionMark := func(s string) rune {
if len(s) == 0 {
return '?'
@ -377,6 +424,323 @@ func (s *settings) renderCustomLayout(ctx ui.Context) {
ctx.Textures().CreateTextureGo("layout-custom", generateCustomKeys(ctx.Resources(), customKeys), true)
}
func (s *keyboardLayoutSettings) selectLayout() {
s.app.MenuInteraction()
switch s.ActiveLayout {
case 0:
s.SelectedLayout = 0
s.app.Settings.Controls.Type = controlsTypeWASD
case 1:
s.SelectedLayout = 1
s.app.Settings.Controls.Type = controlsTypeArrows
case 2:
s.SelectingCustom = 1
}
}
func (s *keyboardLayoutSettings) setActiveLayout(layout int) {
layout = (layout + 3) % 3
change := layout != s.ActiveLayout
s.ActiveLayout = (layout + 3) % 3
if change {
s.app.MenuInteraction()
}
}
type settings struct {
ui.Proxy
app *appContext
musicVolume *volumeControl
soundVolume *volumeControl
keyboard *keyboardLayoutSettings
}
func newSettings(app *appContext, ctx ui.Context) *settings {
volumeControlTexture := generateTextureMapFromImage(ctx.Textures(), "volume-control", generateVolumeControlTexture)
settings := &settings{app: app,
musicVolume: newVolumeControl(app, volumeControlTexture, "Music", app.Settings.Audio.MusicVolume, func(volume float64) { app.setMusicVolume(volume) }),
soundVolume: newVolumeControl(app, volumeControlTexture, "Sounds", app.Settings.Audio.SoundVolume, func(volume float64) { app.setSoundVolume(volume) }),
keyboard: newKeyboardLayoutSettings(app, ctx),
}
settings.Content = ui.BuildStackPanel(ui.OrientationVertical, func(p *ui.StackPanel) {
p.AddChild(Center(label("SETTINGS", "title")))
p.AddChild(label("", "score"))
p.AddChild(Center(settings.musicVolume))
p.AddChild(label("", "score"))
p.AddChild(Center(settings.soundVolume))
p.AddChild(label("", "score"))
p.AddChild(settings.keyboard)
})
settings.musicVolume.Active = true
return settings
}
func (s *settings) currentActive() int {
switch {
case s.musicVolume.Active:
return 0
case s.soundVolume.Active:
return 1
case s.keyboard.Active:
return 2
default:
return 0
}
}
func (s *settings) Handle(ctx ui.Context, e ui.Event) bool {
if s.keyboard.Active && s.keyboard.SelectingCustom > 0 {
return s.Proxy.Handle(ctx, e)
}
switch e := e.(type) {
case *ui.KeyDownEvent:
switch e.Key {
case ui.KeyUp:
s.setActive(s.currentActive() - 1)
case ui.KeyDown:
s.setActive(s.currentActive() + 1)
}
}
return s.Proxy.Handle(ctx, e)
}
func (s *settings) Render(ctx ui.Context) {
s.Proxy.Render(ctx)
s.keyboard.PostRender(ctx)
}
func (s *settings) setActive(active int) {
controls := [3]*bool{
&s.musicVolume.Active,
&s.soundVolume.Active,
&s.keyboard.Active,
}
for active < 0 {
active += len(controls)
}
if active > 0 {
active = active % len(controls)
}
s.app.MenuInteraction()
for i := range controls {
*controls[i] = i == active
}
}
func skewedKeyboardCoordinates(x, y float64) (float64, float64) {
return x - keyboardKeySkew*y, y
}
var supportedCustomKeys = map[ui.Key]string{
ui.KeyA: "A",
ui.KeyB: "B",
ui.KeyC: "C",
ui.KeyD: "D",
ui.KeyE: "E",
ui.KeyF: "F",
ui.KeyG: "G",
ui.KeyH: "H",
ui.KeyI: "I",
ui.KeyJ: "J",
ui.KeyK: "K",
ui.KeyL: "L",
ui.KeyM: "M",
ui.KeyN: "N",
ui.KeyO: "O",
ui.KeyP: "P",
ui.KeyQ: "Q",
ui.KeyR: "R",
ui.KeyS: "S",
ui.KeyT: "T",
ui.KeyU: "U",
ui.KeyV: "V",
ui.KeyW: "W",
ui.KeyX: "X",
ui.KeyY: "Y",
ui.KeyZ: "Z",
}
type volumeControl struct {
ui.ControlBase
app *appContext
texture *TextureMap
Active bool
overLeftKnob bool
overBar int
overRightKnob bool
Name string
Volume float64
VolumeChanged func(float64)
}
func newVolumeControl(app *appContext, texture *TextureMap, name string, volume float64, changed func(float64)) *volumeControl {
control := &volumeControl{app: app, texture: texture, overBar: -1, Name: name, Volume: volume, VolumeChanged: changed}
return control
}
func (c *volumeControl) changeVolume(delta float64) {
c.setVolume(c.Volume + delta)
}
func (c *volumeControl) setVolume(volume float64) {
volume = geom.Min(maxVolume, geom.Max(minVolume, volume))
if c.Volume == volume {
return
}
c.Volume = volume
c.VolumeChanged(c.Volume)
c.app.MenuInteraction()
}
func (c *volumeControl) DesiredSize(ctx ui.Context, size geom.PointF32) geom.PointF32 {
font := ctx.Fonts().Font("default")
return geom.PtF32(geom.NaN32(), 2.5*font.Height())
}
func (c *volumeControl) Handle(ctx ui.Context, e ui.Event) bool {
if c.ControlBase.Handle(ctx, e) {
return true
}
switch e := e.(type) {
case *ui.KeyDownEvent:
switch e.Key {
case ui.KeyLeft:
if c.Active {
c.changeVolume(-1)
}
case ui.KeyRight:
if c.Active {
c.changeVolume(1)
}
}
case *ui.MouseMoveEvent:
over := c.isOver(ctx, e.Pos())
switch over {
case -1:
c.overLeftKnob = false
c.overBar = -1
c.overRightKnob = false
case 0:
if !c.overLeftKnob {
c.app.MenuInteraction()
}
c.overLeftKnob = true
c.overBar = -1
c.overRightKnob = false
case 11:
if !c.overRightKnob {
c.app.MenuInteraction()
}
c.overLeftKnob = false
c.overBar = -1
c.overRightKnob = true
default:
if over-1 != c.overBar {
c.app.MenuInteraction()
}
c.overLeftKnob = false
c.overBar = over - 1
c.overRightKnob = false
}
c.Active = c.IsOver()
case *ui.MouseButtonDownEvent:
if e.Button == ui.MouseButtonLeft {
over := c.isOver(ctx, e.Pos())
switch over {
case -1:
case 0:
c.changeVolume(-1)
case 11:
c.changeVolume(1)
default:
c.setVolume(float64(over + minVolume))
}
}
}
return false
}
func (c *volumeControl) isOver(ctx ui.Context, p geom.PointF32) int {
bounds := c.Bounds()
font := ctx.Fonts().Font("default")
top := bounds.Min.Y + 1.5*font.Height()
bottom := top + font.Height()
if p.Y < top || p.Y >= bottom {
return -1
}
scale := tins2021.FindScaleRound(volumeControlTextureHeight, font.Height())
knobWidth := scale * volumeControlKnobWidth
barWidth := scale * volumeControlBarWidth
left := bounds.Center().X - 5*barWidth - knobWidth
if p.X < left {
return -1
}
widths := []float32{knobWidth, barWidth, barWidth, barWidth, barWidth, barWidth, barWidth, barWidth, barWidth, barWidth, barWidth, knobWidth}
for i, w := range widths {
right := left + w
if p.X < right {
return i
}
left = right
}
return -1
}
func (c *volumeControl) Render(ctx ui.Context) {
font := ctx.Fonts().Font("default")
scale := tins2021.FindScaleRound(volumeControlTextureHeight, font.Height())
normalColor := ctx.Style().Palette.Text
activeColor := ctx.Style().Palette.Primary
bounds := c.Bounds()
center := bounds.Center()
renderer := ctx.Renderer()
top := bounds.Min.Y
fontColor := normalColor
if c.Active {
fontColor = activeColor
}
renderer.TextAlign(font, geom.PtF32(center.X, top), fontColor, c.Name, ui.AlignCenter)
tint := func(active bool) color.Color {
if active {
return activeColor
}
return nil
}
leftKnob, leftKnobRegion := c.texture.Scaled("leftKnob", scale)
leftKnobWidth := leftKnobRegion.Dx()
bar, barRegion := c.texture.Scaled("bar", scale)
barFilled, barFilledRegion := c.texture.Scaled("barFilled", scale)
barWidth := barRegion.Dx()
rightKnob, rightKnobRegion := c.texture.Scaled("rightKnob", scale)
left := center.X - 5*barWidth - leftKnobWidth
top += 1.5 * font.Height()
ctx.Renderer().DrawTexturePointOptions(leftKnob, geom.PtF32(left, top), ui.DrawOptions{Source: &leftKnobRegion, Tint: tint(c.overLeftKnob)})
left += leftKnobWidth
for i := 0; i < 10; i++ {
volume := float64(i + minVolume + 1)
if volume <= c.Volume {
ctx.Renderer().DrawTexturePointOptions(barFilled, geom.PtF32(left, top), ui.DrawOptions{Source: &barFilledRegion, Tint: tint(i == c.overBar)})
} else {
ctx.Renderer().DrawTexturePointOptions(bar, geom.PtF32(left, top), ui.DrawOptions{Source: &barRegion, Tint: tint(i == c.overBar)})
}
left += barWidth
}
ctx.Renderer().DrawTexturePointOptions(rightKnob, geom.PtF32(left, top), ui.DrawOptions{Source: &rightKnobRegion, Tint: tint(c.overRightKnob)})
}

View File

@ -0,0 +1,42 @@
package main
import (
"image"
"opslag.de/schobers/geom"
"opslag.de/schobers/tins2021"
"opslag.de/schobers/zntg/ui"
)
type TextureMap struct {
texture tins2021.NamedTexture
regions map[string]geom.RectangleF32
}
func newTextureMapFromImage(textures *ui.Textures, name string, im image.Image, regions map[string]geom.RectangleF) *TextureMap {
texture, err := tins2021.CreateNamedTextureImage(textures, name, im)
if err != nil {
panic(err)
}
regionMap := map[string]geom.RectangleF32{}
for name, region := range regions {
regionMap[name] = region.ToF32()
}
return &TextureMap{
texture: texture,
regions: regionMap,
}
}
func generateTextureMapFromImage(textures *ui.Textures, name string, generate func() (image.Image, map[string]geom.RectangleF)) *TextureMap {
im, regions := generate()
return newTextureMapFromImage(textures, name, im, regions)
}
func (m *TextureMap) Texture(name string) (ui.Texture, geom.RectangleF32) {
return m.texture.Texture(), m.regions[name]
}
func (m *TextureMap) Scaled(name string, scale float32) (ui.Texture, geom.RectangleF32) {
return m.texture.Scaled(scale), geom.RectangleF32{Min: m.regions[name].Min.Mul(scale), Max: m.regions[name].Max.Mul(scale)}
}

View File

@ -23,7 +23,11 @@ func (h Highscores) AddScore(score, difficulty int) (Highscores, bool) {
return append(h, highscore), true
}
if rank < 10 {
return append(h[:rank], append([]Score{highscore}, h[rank:highscores-1]...)...), true
h = append(h[:rank], append([]Score{highscore}, h[rank:highscores]...)...)
if len(h) > 10 {
h = h[:10]
}
return h, true
}
return h, false
}

View File

@ -21,6 +21,16 @@ func newFullHighscore() Highscores {
}
}
func newHalfEmptyHighscore() Highscores {
return Highscores{
NewScore(100, 100),
NewScore(90, 90),
NewScore(80, 80),
NewScore(70, 70),
NewScore(60, 60),
}
}
func TestAddScoreBelowBottom(t *testing.T) {
h := newFullHighscore()
updated, high := h.AddScore(1, 1)
@ -61,6 +71,14 @@ func TestAddScoreMiddle(t *testing.T) {
assert.Equal(t, 51, updated[5].Score)
}
func TestAddScoreMiddleNotFull(t *testing.T) {
h := newHalfEmptyHighscore()
updated, high := h.AddScore(71, 71)
assert.True(t, high)
assert.Len(t, updated, 6)
assert.Equal(t, 71, updated[3].Score)
}
func TestAddScoreTop(t *testing.T) {
h := newFullHighscore()
updated, high := h.AddScore(101, 101)

View File

@ -8,7 +8,21 @@ import (
const settingsFileName = "settings.json"
type AudioSettings struct {
SoundVolume float64
MusicVolume float64
}
type ControlsSettings struct {
Type string
MoveDownRight string
MoveDownLeft string
MoveUpLeft string
MoveUpRight string
}
type Settings struct {
Audio AudioSettings
Controls ControlsSettings
Window WindowSettings
}
@ -25,14 +39,6 @@ func (s *Settings) Store() error {
return SaveUserFileJSON(settingsFileName, s)
}
type ControlsSettings struct {
Type string
MoveDownRight string
MoveDownLeft string
MoveUpLeft string
MoveUpRight string
}
type WindowSettings struct {
Location *geom.Point
Size *geom.Point