Added settings in-game.
Added video settings. Added and improved reusable controls. Separated drawing of sprites. Fixed bug that text was right aligned instead of left aligned.
This commit is contained in:
parent
27ad0453c3
commit
11ab3fca0f
@ -14,6 +14,12 @@ type Button struct {
|
||||
TextAlign allg5.HorizontalAlignment
|
||||
}
|
||||
|
||||
func NewButton(text string, onClick func()) *Button {
|
||||
b := &Button{Text: text}
|
||||
b.OnClick = onClick
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *Button) DesiredSize(ctx *Context) geom.PointF32 {
|
||||
font := ctx.Fonts.Get(b.Font)
|
||||
w := font.TextWidth(b.Text)
|
||||
|
23
alui/center.go
Normal file
23
alui/center.go
Normal file
@ -0,0 +1,23 @@
|
||||
package alui
|
||||
|
||||
import (
|
||||
"opslag.de/schobers/geom"
|
||||
)
|
||||
|
||||
type center struct {
|
||||
Proxy
|
||||
}
|
||||
|
||||
func Center(control Control) Control {
|
||||
return ¢er{Proxy: Proxy{Target: control}}
|
||||
}
|
||||
|
||||
func (c *center) Layout(ctx *Context, bounds geom.RectangleF32) {
|
||||
size := c.DesiredSize(ctx)
|
||||
center := bounds.Center()
|
||||
|
||||
size.X = geom.Min32(size.X, bounds.Dx())
|
||||
size.Y = geom.Min32(size.Y, bounds.Dy())
|
||||
size = size.Mul(.5)
|
||||
c.Proxy.Layout(ctx, geom.RectF32(center.X-size.X, center.Y-size.Y, center.X+size.X, center.Y+size.Y))
|
||||
}
|
34
alui/column.go
Normal file
34
alui/column.go
Normal file
@ -0,0 +1,34 @@
|
||||
package alui
|
||||
|
||||
import "opslag.de/schobers/geom"
|
||||
|
||||
type Column struct {
|
||||
Proxy
|
||||
|
||||
panel *StackPanel
|
||||
}
|
||||
|
||||
func NewColumn() *Column {
|
||||
c := &Column{}
|
||||
c.Init()
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Column) AddChild(child ...Control) {
|
||||
c.panel.Children = append(c.panel.Children, child...)
|
||||
}
|
||||
|
||||
func (c *Column) Init() {
|
||||
c.panel = &StackPanel{Orientation: OrientationVertical}
|
||||
c.Proxy.Target = c.panel
|
||||
}
|
||||
|
||||
func (c *Column) Layout(ctx *Context, bounds geom.RectangleF32) {}
|
||||
|
||||
func (c *Column) Render(ctx *Context, bounds geom.RectangleF32) {
|
||||
columnHeight := c.Proxy.DesiredSize(ctx).Y
|
||||
width, center := bounds.Dx(), bounds.Center()
|
||||
columnBounds := geom.RectF32(.25*width, center.Y-.5*columnHeight, .75*width, center.Y+.5*columnHeight)
|
||||
c.Proxy.Layout(ctx, columnBounds)
|
||||
c.Proxy.Render(ctx, columnBounds)
|
||||
}
|
@ -11,11 +11,8 @@ type Container struct {
|
||||
Children []Control
|
||||
}
|
||||
|
||||
func (c *Container) Handle(e allg5.Event) {
|
||||
c.ControlBase.Handle(e)
|
||||
for _, child := range c.Children {
|
||||
child.Handle(e)
|
||||
}
|
||||
func (c *Container) AddChild(child ...Control) {
|
||||
c.Children = append(c.Children, child...)
|
||||
}
|
||||
|
||||
func (c *Container) DesiredSize(ctx *Context) geom.PointF32 {
|
||||
@ -36,6 +33,13 @@ func (c *Container) DesiredSize(ctx *Context) geom.PointF32 {
|
||||
return size
|
||||
}
|
||||
|
||||
func (c *Container) Handle(e allg5.Event) {
|
||||
c.ControlBase.Handle(e)
|
||||
for _, child := range c.Children {
|
||||
child.Handle(e)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Container) Layout(ctx *Context, bounds geom.RectangleF32) {
|
||||
c.ControlBase.Layout(ctx, bounds)
|
||||
for _, child := range c.Children {
|
||||
|
@ -44,7 +44,7 @@ func (f *Fonts) DrawAlignFont(font *allg5.Font, left, top, right float32, color
|
||||
font.Draw(right, top, color, allg5.AlignRight, text)
|
||||
default:
|
||||
left, top = geom.Round32(left), geom.Round32(top)
|
||||
font.Draw(left, top, color, allg5.AlignRight, text)
|
||||
font.Draw(left, top, color, allg5.AlignLeft, text)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,8 @@ var _ Control = &Label{}
|
||||
type Label struct {
|
||||
ControlBase
|
||||
|
||||
Text string
|
||||
Text string
|
||||
TextAlign allg5.HorizontalAlignment
|
||||
}
|
||||
|
||||
func (l *Label) DesiredSize(ctx *Context) geom.PointF32 {
|
||||
@ -29,5 +30,5 @@ func (l *Label) Render(ctx *Context, bounds geom.RectangleF32) {
|
||||
if back != nil {
|
||||
allg5.DrawFilledRectangle(bounds.Min.X, bounds.Min.Y, bounds.Max.X, bounds.Max.Y, *back)
|
||||
}
|
||||
ctx.Fonts.Draw(l.Font, bounds.Min.X+4, bounds.Min.Y+4, *fore, l.Text)
|
||||
ctx.Fonts.DrawAlign(l.Font, bounds.Min.X+4, bounds.Min.Y+4, bounds.Max.X-4, *fore, l.TextAlign, l.Text)
|
||||
}
|
||||
|
23
alui/menu.go
23
alui/menu.go
@ -2,13 +2,11 @@ package alui
|
||||
|
||||
import (
|
||||
"opslag.de/schobers/allg5"
|
||||
"opslag.de/schobers/geom"
|
||||
)
|
||||
|
||||
type Menu struct {
|
||||
Proxy
|
||||
Column
|
||||
|
||||
panel *StackPanel
|
||||
active int
|
||||
buttons []*Button
|
||||
}
|
||||
@ -19,11 +17,6 @@ func NewMenu() *Menu {
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *Menu) Init() {
|
||||
m.panel = &StackPanel{Orientation: OrientationVertical}
|
||||
m.Proxy.Target = m.panel
|
||||
}
|
||||
|
||||
func (m *Menu) Activate(i int) {
|
||||
if len(m.buttons) == 0 || i < 0 {
|
||||
return
|
||||
@ -42,11 +35,11 @@ func (m *Menu) Add(text string, onClick func()) {
|
||||
button.Over = true
|
||||
}
|
||||
m.buttons = append(m.buttons, button)
|
||||
m.panel.Children = append(m.panel.Children, button)
|
||||
m.AddChild(button)
|
||||
}
|
||||
|
||||
func (m *Menu) Handle(e allg5.Event) {
|
||||
m.Proxy.Handle(e)
|
||||
m.Column.Handle(e)
|
||||
|
||||
if len(m.buttons) == 0 {
|
||||
return
|
||||
@ -75,16 +68,6 @@ func (m *Menu) Handle(e allg5.Event) {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Menu) Layout(ctx *Context, bounds geom.RectangleF32) {}
|
||||
|
||||
func (m *Menu) Render(ctx *Context, bounds geom.RectangleF32) {
|
||||
menuHeight := m.Proxy.DesiredSize(ctx).Y
|
||||
width, center := bounds.Dx(), bounds.Center()
|
||||
menuBounds := geom.RectF32(.25*width, center.Y-.5*menuHeight, .75*width, center.Y+.5*menuHeight)
|
||||
m.Proxy.Layout(ctx, menuBounds)
|
||||
m.Proxy.Render(ctx, menuBounds)
|
||||
}
|
||||
|
||||
func (m *Menu) updateActiveButton(active int) {
|
||||
m.active = active
|
||||
for i, btn := range m.buttons {
|
||||
|
@ -13,10 +13,6 @@ type StackPanel struct {
|
||||
Orientation Orientation
|
||||
}
|
||||
|
||||
func (s *StackPanel) Handle(e allg5.Event) {
|
||||
s.Container.Handle(e)
|
||||
}
|
||||
|
||||
func (s *StackPanel) asLength(p geom.PointF32) float32 {
|
||||
switch s.Orientation {
|
||||
case OrientationHorizontal:
|
||||
@ -75,6 +71,10 @@ func (s *StackPanel) DesiredSize(ctx *Context) geom.PointF32 {
|
||||
return size
|
||||
}
|
||||
|
||||
func (s *StackPanel) Handle(e allg5.Event) {
|
||||
s.Container.Handle(e)
|
||||
}
|
||||
|
||||
func (s *StackPanel) Layout(ctx *Context, bounds geom.RectangleF32) {
|
||||
s.Container.Layout(ctx, bounds)
|
||||
|
||||
|
317
cmd/krampus19/changesettings.go
Normal file
317
cmd/krampus19/changesettings.go
Normal file
@ -0,0 +1,317 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"opslag.de/schobers/krampus19/gut"
|
||||
|
||||
"opslag.de/schobers/geom"
|
||||
|
||||
"opslag.de/schobers/allg5"
|
||||
"opslag.de/schobers/krampus19/alui"
|
||||
)
|
||||
|
||||
const margin = 8
|
||||
|
||||
type displayModeControl struct {
|
||||
alui.StackPanel
|
||||
|
||||
label alui.Label
|
||||
|
||||
modes []string
|
||||
current int
|
||||
}
|
||||
|
||||
func newDisplayModeControl(ctx *Context) *displayModeControl {
|
||||
var modes []string
|
||||
fmtDisplayMode := func(width, height int) string { return fmt.Sprintf("%d x %d", width, height) }
|
||||
containsDisplayMode := func(mode string) bool {
|
||||
for _, m := range modes {
|
||||
if m == mode {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
displayMode := ctx.Settings.Video.DisplayMode
|
||||
if displayMode == "" {
|
||||
displayMode = fmtDisplayMode(ctx.DisplaySize.X, ctx.DisplaySize.Y)
|
||||
}
|
||||
var current int
|
||||
for _, m := range allg5.DisplayModes() {
|
||||
mode := fmtDisplayMode(m.Width, m.Height)
|
||||
if containsDisplayMode(mode) {
|
||||
continue
|
||||
}
|
||||
if mode == displayMode {
|
||||
current = len(modes)
|
||||
}
|
||||
modes = append(modes, mode)
|
||||
}
|
||||
|
||||
c := &displayModeControl{modes: modes, current: current}
|
||||
c.Orientation = alui.OrientationHorizontal
|
||||
c.label.TextAlign = allg5.AlignCenter
|
||||
c.selectMode(0)
|
||||
|
||||
c.AddChild(newSpriteButton(ctx, "ui", "angle-left", func() { c.selectMode(-1) }))
|
||||
c.AddChild(&c.label)
|
||||
c.AddChild(newSpriteButton(ctx, "ui", "angle-right", func() { c.selectMode(1) }))
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *displayModeControl) selectMode(delta int) {
|
||||
c.current = (c.current + delta + len(c.modes)) % len(c.modes)
|
||||
c.label.Text = c.Mode()
|
||||
}
|
||||
|
||||
func (c *displayModeControl) Mode() string { return c.modes[c.current] }
|
||||
|
||||
type checkBox struct {
|
||||
alui.Button
|
||||
|
||||
ctx *Context
|
||||
|
||||
Selected bool
|
||||
}
|
||||
|
||||
func newCheckBox(ctx *Context) *checkBox {
|
||||
b := &checkBox{ctx: ctx}
|
||||
b.OnClick = func() {
|
||||
b.Selected = !b.Selected
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *checkBox) DesiredSize(ctx *alui.Context) geom.PointF32 {
|
||||
return b.ctx.SpriteDrawer.Size("ui", "check-square").Add2D(2*margin, 2*margin)
|
||||
}
|
||||
|
||||
func (b *checkBox) Render(ctx *alui.Context, bounds geom.RectangleF32) {
|
||||
tint := b.ctx.Palette.Primary
|
||||
if b.Over {
|
||||
tint = b.ctx.Palette.Dark
|
||||
ctx.Cursor = allg5.MouseCursorLink
|
||||
}
|
||||
var part = "square"
|
||||
if b.Selected {
|
||||
part = "check-square"
|
||||
}
|
||||
b.ctx.SpriteDrawer.Draw("ui", part, bounds.Center(), DrawSpriteOptions{Tint: &tint})
|
||||
}
|
||||
|
||||
type spriteButton struct {
|
||||
alui.Button
|
||||
|
||||
ctx *Context
|
||||
|
||||
Sprite string
|
||||
Part string
|
||||
}
|
||||
|
||||
func newSpriteButton(ctx *Context, sprite, part string, onClick func()) *spriteButton {
|
||||
b := &spriteButton{ctx: ctx, Sprite: sprite, Part: part}
|
||||
b.OnClick = onClick
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *spriteButton) DesiredSize(ctx *alui.Context) geom.PointF32 {
|
||||
return b.ctx.SpriteDrawer.Size(b.Sprite, b.Part).Add2D(2*margin, 2*margin)
|
||||
}
|
||||
|
||||
func (b *spriteButton) Render(ctx *alui.Context, bounds geom.RectangleF32) {
|
||||
tint := b.ctx.Palette.Primary
|
||||
if b.Over {
|
||||
tint = b.ctx.Palette.Dark
|
||||
ctx.Cursor = allg5.MouseCursorLink
|
||||
}
|
||||
b.ctx.SpriteDrawer.Draw(b.Sprite, b.Part, bounds.Center(), DrawSpriteOptions{Tint: &tint})
|
||||
}
|
||||
|
||||
type selectKeyControl struct {
|
||||
alui.Label
|
||||
|
||||
Key allg5.Key
|
||||
|
||||
WaitingForInput bool
|
||||
}
|
||||
|
||||
func newSelectKeyControl(key allg5.Key) *selectKeyControl {
|
||||
c := &selectKeyControl{Key: key}
|
||||
c.TextAlign = allg5.AlignCenter
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *selectKeyControl) Handle(e allg5.Event) {
|
||||
c.Label.Handle(e)
|
||||
|
||||
switch e := e.(type) {
|
||||
case *allg5.MouseButtonDownEvent:
|
||||
if c.Over && e.Button == allg5.MouseButtonLeft {
|
||||
c.WaitingForInput = true
|
||||
}
|
||||
case *allg5.KeyDownEvent:
|
||||
if c.WaitingForInput {
|
||||
if e.KeyCode != allg5.KeyEscape {
|
||||
c.Key = e.KeyCode
|
||||
}
|
||||
c.WaitingForInput = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *selectKeyControl) Render(ctx *alui.Context, bounds geom.RectangleF32) {
|
||||
if c.WaitingForInput {
|
||||
c.Text = "_"
|
||||
} else {
|
||||
c.Text = gut.KeyToString(c.Key)
|
||||
}
|
||||
if c.Over {
|
||||
ctx.Cursor = allg5.MouseCursorLink
|
||||
}
|
||||
c.Label.Render(ctx, bounds)
|
||||
}
|
||||
|
||||
type settingsHeader struct {
|
||||
alui.Label
|
||||
}
|
||||
|
||||
func newSettingsHeader(label string) alui.Control {
|
||||
header := &settingsHeader{}
|
||||
header.Text = label
|
||||
return header
|
||||
}
|
||||
|
||||
func (h *settingsHeader) DesiredSize(ctx *alui.Context) geom.PointF32 {
|
||||
size := h.Label.DesiredSize(ctx)
|
||||
size.Y += 5 * margin
|
||||
return size
|
||||
}
|
||||
|
||||
func (h *settingsHeader) Layout(ctx *alui.Context, bounds geom.RectangleF32) {
|
||||
var label = bounds
|
||||
if label.Dy() > 5*margin {
|
||||
label.Min.Y = bounds.Min.Y + 3*margin
|
||||
label.Max.Y = bounds.Max.Y - 2*margin
|
||||
}
|
||||
h.Label.Layout(ctx, label)
|
||||
}
|
||||
|
||||
type settingsRow struct {
|
||||
alui.StackPanel
|
||||
|
||||
label *alui.Label
|
||||
edit alui.Control
|
||||
}
|
||||
|
||||
func newSettingRow(label string, edit alui.Control) *settingsRow {
|
||||
row := &settingsRow{}
|
||||
row.Orientation = alui.OrientationHorizontal
|
||||
row.label = &alui.Label{Text: label}
|
||||
row.edit = edit
|
||||
row.Children = append(row.Children, row.label, row.edit)
|
||||
return row
|
||||
}
|
||||
|
||||
func (r *settingsRow) Layout(ctx *alui.Context, bounds geom.RectangleF32) {
|
||||
label := bounds
|
||||
label.Max.X = label.Min.X + .5*label.Dx()
|
||||
r.label.Layout(ctx, label)
|
||||
|
||||
width := r.edit.DesiredSize(ctx).X
|
||||
edit := bounds
|
||||
if geom.IsNaN32(width) || width > label.Dx() {
|
||||
edit.Min.X = label.Max.X
|
||||
} else {
|
||||
margin := (label.Dx() - width) * .5
|
||||
edit.Min.X = label.Max.X + margin
|
||||
edit.Max.X -= margin
|
||||
}
|
||||
r.edit.Layout(ctx, edit)
|
||||
}
|
||||
|
||||
type wideButton struct {
|
||||
alui.Button
|
||||
}
|
||||
|
||||
func newWideButton(label string, onClick func()) *wideButton {
|
||||
b := &wideButton{}
|
||||
b.Text = label
|
||||
b.TextAlign = allg5.AlignCenter
|
||||
b.OnClick = onClick
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *wideButton) DesiredSize(ctx *alui.Context) geom.PointF32 {
|
||||
size := b.Button.DesiredSize(ctx)
|
||||
size.X += 2 * margin
|
||||
return size
|
||||
}
|
||||
|
||||
type changeSettings struct {
|
||||
alui.Column
|
||||
|
||||
ctx *Context
|
||||
|
||||
rows []*settingsRow
|
||||
active int
|
||||
}
|
||||
|
||||
func (s *changeSettings) addRow(row ...*settingsRow) {
|
||||
for _, row := range row {
|
||||
s.AddChild(row)
|
||||
s.rows = append(s.rows, row)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *changeSettings) Enter(ctx *Context) error {
|
||||
s.Init()
|
||||
s.ctx = ctx
|
||||
|
||||
keyLeft := newSelectKeyControl(s.ctx.Settings.Controls.MoveLeft)
|
||||
keyUp := newSelectKeyControl(s.ctx.Settings.Controls.MoveUp)
|
||||
keyRight := newSelectKeyControl(s.ctx.Settings.Controls.MoveRight)
|
||||
keyDown := newSelectKeyControl(s.ctx.Settings.Controls.MoveDown)
|
||||
|
||||
s.AddChild(newSettingsHeader("Controls"))
|
||||
s.addRow(
|
||||
newSettingRow("Key left", keyLeft),
|
||||
newSettingRow("Key up", keyUp),
|
||||
newSettingRow("Key right", keyRight),
|
||||
newSettingRow("Key down", keyDown),
|
||||
)
|
||||
s.AddChild(newSettingsHeader("Video"))
|
||||
|
||||
displayMode := newDisplayModeControl(ctx)
|
||||
windowed := newCheckBox(ctx)
|
||||
windowed.Selected = s.ctx.Settings.Video.Windowed
|
||||
|
||||
s.addRow(
|
||||
newSettingRow("Windowed", windowed),
|
||||
newSettingRow("Resolution (fullscreen)", displayMode),
|
||||
)
|
||||
buttons := &alui.StackPanel{Orientation: alui.OrientationHorizontal}
|
||||
buttons.AddChild(
|
||||
newWideButton("Apply", func() {
|
||||
var controls = controls{MoveLeft: keyLeft.Key, MoveUp: keyUp.Key, MoveRight: keyRight.Key, MoveDown: keyDown.Key}
|
||||
var video = video{Windowed: windowed.Selected, DisplayMode: displayMode.Mode()}
|
||||
s.ctx.Settings = settings{Controls: controls, Video: video}
|
||||
err := s.ctx.Settings.StoreDefault()
|
||||
if err != nil {
|
||||
log.Printf("User settings are not stored: %v", err)
|
||||
} else {
|
||||
log.Printf("Stored new settings.")
|
||||
}
|
||||
|
||||
s.ctx.Navigation.showMainMenu()
|
||||
}),
|
||||
newWideButton("Cancel", func() { s.ctx.Navigation.showMainMenu() }),
|
||||
)
|
||||
s.AddChild(alui.Center(buttons))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *changeSettings) Leave() {
|
||||
}
|
@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"time"
|
||||
|
||||
"opslag.de/schobers/geom"
|
||||
"opslag.de/schobers/krampus19/alui"
|
||||
|
||||
"opslag.de/schobers/allg5"
|
||||
@ -20,12 +21,14 @@ func newTexture(bmp *allg5.Bitmap) texture {
|
||||
}
|
||||
|
||||
type Context struct {
|
||||
Resources vfs.CopyDir
|
||||
Textures map[string]texture
|
||||
Levels map[string]level
|
||||
Sprites map[string]sprite
|
||||
Settings settings
|
||||
Palette *alui.Palette
|
||||
DisplaySize geom.Point
|
||||
Resources vfs.CopyDir
|
||||
Textures map[string]texture
|
||||
Levels map[string]level
|
||||
Sprites map[string]sprite
|
||||
SpriteDrawer SpriteDrawer
|
||||
Settings settings
|
||||
Palette *alui.Palette
|
||||
|
||||
Tick time.Duration
|
||||
Navigation navigation
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
|
||||
"opslag.de/schobers/allg5"
|
||||
"opslag.de/schobers/fs/vfs"
|
||||
"opslag.de/schobers/geom"
|
||||
"opslag.de/schobers/krampus19/alui"
|
||||
"opslag.de/schobers/krampus19/gut"
|
||||
)
|
||||
@ -141,6 +142,8 @@ func (g *game) loadAssets() error {
|
||||
|
||||
"tile_lava_brick.png": "lava_brick",
|
||||
"tile_magma.png": "magma",
|
||||
|
||||
"ui.png": "ui",
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@ -162,7 +165,7 @@ func (g *game) loadAssets() error {
|
||||
log.Printf("Loaded %d fonts.\n", g.ui.Fonts().Len())
|
||||
|
||||
log.Println("Loading sprites")
|
||||
err = g.loadSprites("brick", "lava_brick", "magma", "main_character")
|
||||
err = g.loadSprites("brick", "lava_brick", "magma", "main_character", "ui")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -175,9 +178,11 @@ func (g *game) Destroy() {
|
||||
g.ctx.Destroy()
|
||||
}
|
||||
|
||||
func (g *game) Init(disp *allg5.Display, res vfs.CopyDir, cons *gut.Console, fps *gut.FPS) error {
|
||||
func (g *game) Init(disp *allg5.Display, settings settings, res vfs.CopyDir, cons *gut.Console, fps *gut.FPS) error {
|
||||
log.Print("Initializing game...")
|
||||
g.ctx = &Context{Resources: res, Textures: map[string]texture{}, Settings: newDefaultSettings(), Navigation: navigation{game: g}}
|
||||
g.ctx = &Context{Resources: res, Textures: map[string]texture{}, Settings: settings, Navigation: navigation{game: g}}
|
||||
g.ctx.DisplaySize = geom.Pt(disp.Width(), disp.Height())
|
||||
g.ctx.SpriteDrawer.ctx = g.ctx
|
||||
if err := g.initUI(disp, cons, fps); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"time"
|
||||
@ -10,6 +11,7 @@ import (
|
||||
"opslag.de/schobers/allg5"
|
||||
"opslag.de/schobers/fs/ricefs"
|
||||
"opslag.de/schobers/fs/vfs"
|
||||
"opslag.de/schobers/geom"
|
||||
"opslag.de/schobers/krampus19/gut"
|
||||
)
|
||||
|
||||
@ -33,14 +35,39 @@ func run() error {
|
||||
cons := &gut.Console{}
|
||||
log.SetOutput(io.MultiWriter(log.Writer(), cons))
|
||||
|
||||
log.Printf("Initializing Allegro")
|
||||
log.Printf("Initializing Allegro.")
|
||||
err := allg5.Init(allg5.InitAll)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("Creating display")
|
||||
disp, err := allg5.NewDisplay(1440, 900, allg5.NewDisplayOptions{Maximized: false, Windowed: true, Resizable: true, Vsync: true})
|
||||
settings := newDefaultSettings()
|
||||
err = settings.LoadDefault()
|
||||
if err != nil {
|
||||
log.Printf("Unable to load settings, falling back on defaults.")
|
||||
}
|
||||
err = settings.StoreDefault()
|
||||
if err != nil {
|
||||
log.Printf("Unable to store settings.")
|
||||
}
|
||||
|
||||
log.Printf("Creating display.")
|
||||
var size = geom.Pt(1280, 720)
|
||||
dispOptions := allg5.NewDisplayOptions{Vsync: true}
|
||||
if settings.Video.Windowed {
|
||||
dispOptions.Maximized = true
|
||||
dispOptions.Frameless = true
|
||||
dispOptions.Windowed = true
|
||||
} else {
|
||||
dispOptions.Fullscreen = true
|
||||
str := func(m allg5.DisplayMode) string { return fmt.Sprintf("%d x %d", m.Width, m.Height) }
|
||||
for _, mode := range allg5.DisplayModes() {
|
||||
if str(mode) == settings.Video.DisplayMode {
|
||||
size = geom.Pt(mode.Width, mode.Height)
|
||||
}
|
||||
}
|
||||
}
|
||||
disp, err := allg5.NewDisplay(size.X, size.Y, dispOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -66,7 +93,7 @@ func run() error {
|
||||
defer fps.Destroy()
|
||||
|
||||
game := &game{}
|
||||
err = game.Init(disp, res, cons, fps)
|
||||
err = game.Init(disp, settings, res, cons, fps)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -94,6 +121,8 @@ func run() error {
|
||||
log.Printf("Stopping game loop, user pressed Alt+F4")
|
||||
return nil
|
||||
}
|
||||
case *allg5.DisplayResizeEvent:
|
||||
game.ctx.DisplaySize = geom.Pt(disp.Width(), disp.Height())
|
||||
}
|
||||
game.Handle(e)
|
||||
e = eq.Get()
|
||||
|
@ -14,6 +14,7 @@ func (m *mainMenu) Enter(ctx *Context) error {
|
||||
m.ctx = ctx
|
||||
m.Init()
|
||||
m.Add("Play", func() { m.ctx.Navigation.playLevel("1") })
|
||||
m.Add("Settings", func() { m.ctx.Navigation.changeSettings() })
|
||||
m.Add("Quit", func() { m.ctx.Navigation.quit() })
|
||||
return nil
|
||||
}
|
||||
|
@ -16,6 +16,10 @@ type scene interface {
|
||||
Leave()
|
||||
}
|
||||
|
||||
func (n *navigation) changeSettings() {
|
||||
n.switchTo(&changeSettings{})
|
||||
}
|
||||
|
||||
func (n *navigation) playLevel(l string) {
|
||||
n.switchTo(&playLevel{name: l})
|
||||
}
|
||||
@ -34,6 +38,7 @@ func (n *navigation) switchTo(s scene) {
|
||||
}
|
||||
n.curr = s
|
||||
n.game.scene.Proxy = s
|
||||
n.game.scene.Visible = s != nil
|
||||
if n.curr != nil {
|
||||
n.curr.Enter(n.game.ctx)
|
||||
}
|
||||
|
@ -165,32 +165,12 @@ func (l *playLevel) Handle(e allg5.Event) {
|
||||
}
|
||||
}
|
||||
|
||||
func (l *playLevel) drawSpritePart(name, partName string, pos geom.PointF32) {
|
||||
l.drawSpritePartOffset(name, partName, pos, 0)
|
||||
func (l *playLevel) drawSprite(name, partName string, pos geom.PointF32) {
|
||||
l.drawSpritePart(name, partName, pos, 0)
|
||||
}
|
||||
|
||||
func (l *playLevel) drawSpritePartOffset(name, partName string, pos geom.PointF32, z float32) {
|
||||
sprite, ok := l.ctx.Sprites[name]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
text, ok := l.ctx.Textures[sprite.texture]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
partText, ok := text.Subs[partName]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
part := sprite.FindPartByName(partName)
|
||||
scale := l.scale
|
||||
if part.scale != 0 {
|
||||
scale *= 1. / part.scale
|
||||
}
|
||||
anchor := part.sub.Min.Sub(part.anchor).ToF32().Mul(scale)
|
||||
scrPos := l.posToScreenF32(pos, z).Add(anchor)
|
||||
left, top := scrPos.X, scrPos.Y
|
||||
partText.DrawOptions(left, top, allg5.DrawOptions{Scale: allg5.NewUniformScale(scale)})
|
||||
func (l *playLevel) drawSpritePart(name, partName string, pos geom.PointF32, z float32) {
|
||||
l.ctx.SpriteDrawer.Draw(name, partName, l.posToScreenF32(pos, z), DrawSpriteOptions{Scale: l.scale})
|
||||
}
|
||||
|
||||
func (l *playLevel) Render(ctx *alui.Context, bounds geom.RectangleF32) {
|
||||
@ -200,15 +180,15 @@ func (l *playLevel) Render(ctx *alui.Context, bounds geom.RectangleF32) {
|
||||
switch t {
|
||||
case tileBasic:
|
||||
if l.state.IsNextToMagma(pos) {
|
||||
l.drawSpritePart("lava_brick", "magma", pos.ToF32())
|
||||
l.drawSprite("lava_brick", "magma", pos.ToF32())
|
||||
} else {
|
||||
l.drawSpritePart("lava_brick", "lava_brick", pos.ToF32())
|
||||
l.drawSprite("lava_brick", "lava_brick", pos.ToF32())
|
||||
}
|
||||
case tileMagma:
|
||||
l.drawSpritePart("magma", "magma", pos.ToF32())
|
||||
l.drawSprite("magma", "magma", pos.ToF32())
|
||||
if l.state.IsFilledUp(pos) {
|
||||
l.drawSpritePartOffset("brick", "brick", pos.ToF32(), 80)
|
||||
l.drawSpritePart("magma", "sunken_overlay", pos.ToF32())
|
||||
l.drawSpritePart("brick", "brick", pos.ToF32(), 80)
|
||||
l.drawSprite("magma", "sunken_overlay", pos.ToF32())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -224,9 +204,9 @@ func (l *playLevel) Render(ctx *alui.Context, bounds geom.RectangleF32) {
|
||||
for _, e := range entities {
|
||||
switch e.typ {
|
||||
case entityTypeCharacter:
|
||||
l.drawSpritePart("main_character", "main_character", e.scr)
|
||||
l.drawSprite("main_character", "main_character", e.scr)
|
||||
case entityTypeBrick:
|
||||
l.drawSpritePart("brick", "brick", e.scr)
|
||||
l.drawSprite("brick", "brick", e.scr)
|
||||
}
|
||||
}
|
||||
|
||||
|
32
cmd/krampus19/res/sprites/ui.txt
Normal file
32
cmd/krampus19/res/sprites/ui.txt
Normal file
@ -0,0 +1,32 @@
|
||||
sprite:
|
||||
texture: ui
|
||||
|
||||
part:
|
||||
name: square
|
||||
sub_texture: 0,0,448,512
|
||||
anchor: 224,256
|
||||
scale: 16
|
||||
:part
|
||||
|
||||
part:
|
||||
name: check-square
|
||||
sub_texture: 0,512,448,512
|
||||
anchor: 224,768
|
||||
scale: 16
|
||||
:part
|
||||
|
||||
part:
|
||||
name: angle-left
|
||||
sub_texture: 768,0,192,512
|
||||
anchor: 848,196
|
||||
scale: 16
|
||||
:part
|
||||
|
||||
part:
|
||||
name: angle-right
|
||||
sub_texture: 768,512,192,512
|
||||
anchor: 848,720
|
||||
scale: 16
|
||||
:part
|
||||
|
||||
:sprite
|
BIN
cmd/krampus19/res/ui.png
Normal file
BIN
cmd/krampus19/res/ui.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 33 KiB |
@ -1,10 +1,12 @@
|
||||
package main
|
||||
|
||||
import "opslag.de/schobers/allg5"
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
type settings struct {
|
||||
Controls controls
|
||||
}
|
||||
"opslag.de/schobers/allg5"
|
||||
)
|
||||
|
||||
func newDefaultSettings() settings {
|
||||
return settings{
|
||||
@ -14,9 +16,71 @@ func newDefaultSettings() settings {
|
||||
MoveDown: allg5.KeyDown,
|
||||
MoveLeft: allg5.KeyLeft,
|
||||
},
|
||||
Video: video{
|
||||
Windowed: true,
|
||||
DisplayMode: "",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type settings struct {
|
||||
Controls controls
|
||||
Video video
|
||||
}
|
||||
|
||||
func (s *settings) DefaultPath() (string, error) {
|
||||
config, err := os.UserConfigDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
dir := filepath.Join(config, "krampus19")
|
||||
err = os.MkdirAll(dir, 0600)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Join(dir, "settings.json"), nil
|
||||
}
|
||||
|
||||
func (s *settings) Load(path string) error {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
var fromFile settings
|
||||
err = json.NewDecoder(f).Decode(&fromFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*s = fromFile
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *settings) LoadDefault() error {
|
||||
path, err := s.DefaultPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.Load(path)
|
||||
}
|
||||
|
||||
func (s *settings) Store(path string) error {
|
||||
f, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
return json.NewEncoder(f).Encode(&s)
|
||||
}
|
||||
|
||||
func (s *settings) StoreDefault() error {
|
||||
path, err := s.DefaultPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.Store(path)
|
||||
}
|
||||
|
||||
type controls struct {
|
||||
MoveUp allg5.Key
|
||||
MoveRight allg5.Key
|
||||
@ -24,6 +88,11 @@ type controls struct {
|
||||
MoveLeft allg5.Key
|
||||
}
|
||||
|
||||
type video struct {
|
||||
Windowed bool
|
||||
DisplayMode string
|
||||
}
|
||||
|
||||
func (c controls) MovementKeys() []allg5.Key {
|
||||
return []allg5.Key{c.MoveUp, c.MoveRight, c.MoveDown, c.MoveLeft}
|
||||
}
|
||||
|
69
cmd/krampus19/spritedrawer.go
Normal file
69
cmd/krampus19/spritedrawer.go
Normal file
@ -0,0 +1,69 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"opslag.de/schobers/allg5"
|
||||
"opslag.de/schobers/geom"
|
||||
)
|
||||
|
||||
type DrawSpriteOptions struct {
|
||||
Scale float32
|
||||
Tint *allg5.Color
|
||||
}
|
||||
|
||||
type SpriteDrawer struct {
|
||||
ctx *Context
|
||||
}
|
||||
|
||||
func (d SpriteDrawer) scale(sprite, user float32) float32 {
|
||||
var scale float32 = 1
|
||||
if sprite != 0 {
|
||||
scale *= 1 / sprite
|
||||
}
|
||||
if user != 0 {
|
||||
scale *= user
|
||||
}
|
||||
return scale
|
||||
}
|
||||
|
||||
func (d SpriteDrawer) Draw(name, partName string, pos geom.PointF32, opts DrawSpriteOptions) bool {
|
||||
sprite, ok := d.ctx.Sprites[name]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
text, ok := d.ctx.Textures[sprite.texture]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
partText, ok := text.Subs[partName]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
part := sprite.FindPartByName(partName)
|
||||
scale := d.scale(part.scale, opts.Scale)
|
||||
anchor := part.sub.Min.Sub(part.anchor).ToF32().Mul(scale)
|
||||
scrPos := pos.Add(anchor)
|
||||
left, top := scrPos.X, scrPos.Y
|
||||
var drawOpts allg5.DrawOptions
|
||||
if scale != 1 {
|
||||
drawOpts.Scale = allg5.NewUniformScale(scale)
|
||||
}
|
||||
if opts.Tint != nil {
|
||||
drawOpts.Tint = opts.Tint
|
||||
}
|
||||
partText.DrawOptions(left, top, drawOpts)
|
||||
return true
|
||||
}
|
||||
|
||||
func (d SpriteDrawer) Size(name, partName string) geom.PointF32 {
|
||||
return d.SizeScale(name, partName, 0)
|
||||
}
|
||||
|
||||
func (d SpriteDrawer) SizeScale(name, partName string, scale float32) geom.PointF32 {
|
||||
sprite, ok := d.ctx.Sprites[name]
|
||||
if !ok {
|
||||
return geom.PointF32{}
|
||||
}
|
||||
part := sprite.FindPartByName(partName)
|
||||
scale = d.scale(part.scale, scale)
|
||||
return part.sub.Size().ToF32().Mul(scale)
|
||||
}
|
Loading…
Reference in New Issue
Block a user