Compare commits
2 Commits
88e6fc4181
...
65db973699
Author | SHA1 | Date | |
---|---|---|---|
65db973699 | |||
99e87cef6e |
2
TODO.md
2
TODO.md
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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 {
|
||||
|
@ -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() })
|
||||
|
||||
|
@ -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)})
|
||||
}
|
||||
|
42
cmd/tins2021/texturemap.go
Normal file
42
cmd/tins2021/texturemap.go
Normal 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)}
|
||||
}
|
6
score.go
6
score.go
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
22
settings.go
22
settings.go
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user