Demo Running Level as Title Screen Wallpaper

* Load SDL2 fonts from go-bindata storage so we don't have to ship
  external font files on disk.
* Dedupe names of doodads so we don't show double on the front-end
  (go-bindata bundled doodads + those on local filesystem)
* Use go-bindata for accessing wallpaper images.
* Better flashed messages walking you through the Link Tool.
* Stylize the title screen (MainScene) by rendering a live example level
  as the background wallpaper, with mobile doodads in motion.
physics
Noah 2019-06-27 22:54:46 -07:00
parent 54776ec9e1
commit 3d08291bc5
14 changed files with 185 additions and 46 deletions

View File

@ -42,12 +42,12 @@ build-debug:
# `make bindata` generates the embedded binary assets package. # `make bindata` generates the embedded binary assets package.
.PHONY: bindata .PHONY: bindata
bindata: bindata:
go-bindata -pkg bindata -o pkg/bindata/bindata.go assets/... fonts/ go-bindata -pkg bindata -o pkg/bindata/bindata.go assets/...
# `make bindata-dev` generates the debug version of bindata package. # `make bindata-dev` generates the debug version of bindata package.
.PHONY: bindata-dev .PHONY: bindata-dev
bindata-dev: bindata-dev:
go-bindata -debug -pkg bindata -o pkg/bindata/bindata.go assets/... fonts/ go-bindata -debug -pkg bindata -o pkg/bindata/bindata.go assets/...
# `make wasm` builds the WebAssembly port. # `make wasm` builds the WebAssembly port.
.PHONY: wasm .PHONY: wasm
@ -102,7 +102,7 @@ test:
dist: doodads bindata build dist: doodads bindata build
mkdir -p dist/doodle-$(VERSION) mkdir -p dist/doodle-$(VERSION)
cp bin/* dist/doodle-$(VERSION)/ cp bin/* dist/doodle-$(VERSION)/
cp -r assets fonts README.md dist/doodle-$(VERSION)/ cp -r README.md dist/doodle-$(VERSION)/
cd dist && tar -czvf doodle-$(VERSION).tar.gz doodle-$(VERSION) cd dist && tar -czvf doodle-$(VERSION).tar.gz doodle-$(VERSION)
cd dist && zip -r doodle-$(VERSION).zip doodle-$(VERSION) cd dist && zip -r doodle-$(VERSION).zip doodle-$(VERSION)

View File

@ -11,6 +11,7 @@ import (
"git.kirsle.net/apps/doodle/lib/render/sdl" "git.kirsle.net/apps/doodle/lib/render/sdl"
doodle "git.kirsle.net/apps/doodle/pkg" doodle "git.kirsle.net/apps/doodle/pkg"
"git.kirsle.net/apps/doodle/pkg/balance" "git.kirsle.net/apps/doodle/pkg/balance"
"git.kirsle.net/apps/doodle/pkg/bindata"
"git.kirsle.net/apps/doodle/pkg/branding" "git.kirsle.net/apps/doodle/pkg/branding"
"github.com/urfave/cli" "github.com/urfave/cli"
@ -80,6 +81,20 @@ func main() {
balance.Height, balance.Height,
) )
// Load the SDL fonts in from bindata storage.
if fonts, err := bindata.AssetDir("assets/fonts"); err == nil {
for _, file := range fonts {
data, err := bindata.Asset("assets/fonts/" + file)
if err != nil {
panic(err)
}
sdl.InstallFont(file, data)
}
} else {
panic(err)
}
game := doodle.New(c.Bool("debug"), engine) game := doodle.New(c.Bool("debug"), engine)
game.SetupEngine() game.SetupEngine()
if c.Bool("guitest") { if c.Bool("guitest") {

View File

@ -13,7 +13,7 @@ var (
DebugWindowEvents = false DebugWindowEvents = false
DebugMouseEvents = false DebugMouseEvents = false
DebugClickEvents = false DebugClickEvents = false
DebugKeyEvents = true DebugKeyEvents = false
) )
// Poll for events. // Poll for events.

View File

@ -3,6 +3,7 @@ package sdl
import ( import (
"fmt" "fmt"
"strings" "strings"
"sync"
"git.kirsle.net/apps/doodle/lib/events" "git.kirsle.net/apps/doodle/lib/events"
"git.kirsle.net/apps/doodle/lib/render" "git.kirsle.net/apps/doodle/lib/render"
@ -11,9 +12,29 @@ import (
) )
// TODO: font filenames // TODO: font filenames
var defaultFontFilename = "./fonts/DejaVuSans.ttf" var defaultFontFilename = "DejaVuSans.ttf"
var fonts = map[string]*ttf.Font{} // Font holds cached SDL_TTF structures for loaded fonts. They are created
// automatically when fonts are either preinstalled (InstallFont) or loaded for
// the first time as demanded by the DrawText method.
type Font struct {
Filename string
data []byte // raw binary data of font
ttf *ttf.Font
}
var (
fonts = map[string]*ttf.Font{} // keys like "DejaVuSans@14" by font size
installedFont = map[string][]byte{} // installed font files' binary handles
fontsMu sync.RWMutex
)
// InstallFont preloads the font cache using TTF binary data in memory.
func InstallFont(filename string, binary []byte) {
fontsMu.Lock()
installedFont[filename] = binary
fontsMu.Unlock()
}
// LoadFont loads and caches the font at a given size. // LoadFont loads and caches the font at a given size.
func LoadFont(filename string, size int) (*ttf.Font, error) { func LoadFont(filename string, size int) (*ttf.Font, error) {
@ -27,10 +48,30 @@ func LoadFont(filename string, size int) (*ttf.Font, error) {
return font, nil return font, nil
} }
font, err := ttf.OpenFont(filename, size) // Do we have this font in memory?
if err != nil { var (
return nil, err font *ttf.Font
err error
)
if binary, ok := installedFont[filename]; ok {
var RWops *sdl.RWops
RWops, err = sdl.RWFromMem(binary)
if err != nil {
return nil, fmt.Errorf("LoadFont(%s): RWFromMem: %s", filename, err)
}
font, err = ttf.OpenFontRW(RWops, 0, size)
} else {
font, err = ttf.OpenFont(filename, size)
} }
// Error opening the font?
if err != nil {
return nil, fmt.Errorf("LoadFont(%s): %s", filename, err)
}
// Cache this font name and size.
fonts[keyName] = font fonts[keyName] = font
return font, nil return font, nil

View File

@ -16,7 +16,7 @@ var (
***************/ ***************/
// Debug overlay (FPS etc.) settings. // Debug overlay (FPS etc.) settings.
DebugFontFilename = "./fonts/DejaVuSans-Bold.ttf" DebugFontFilename = "DejaVuSans-Bold.ttf"
DebugFontSize = 16 DebugFontSize = 16
DebugLabelColor = render.MustHexColor("#FF9900") DebugLabelColor = render.MustHexColor("#FF9900")
DebugValueColor = render.MustHexColor("#00CCFF") DebugValueColor = render.MustHexColor("#00CCFF")

View File

@ -4,7 +4,7 @@ import "git.kirsle.net/apps/doodle/lib/render"
// Shell related variables. // Shell related variables.
var ( var (
ShellFontFilename = "./fonts/DejaVuSansMono.ttf" ShellFontFilename = "DejaVuSansMono.ttf"
ShellBackgroundColor = render.RGBA(0, 20, 40, 200) ShellBackgroundColor = render.RGBA(0, 20, 40, 200)
ShellForegroundColor = render.RGBA(0, 153, 255, 255) ShellForegroundColor = render.RGBA(0, 153, 255, 255)
ShellPromptColor = render.White ShellPromptColor = render.White

View File

@ -14,7 +14,7 @@ var (
OutlineColor: render.Black, OutlineColor: render.Black,
} }
TitleFont = render.Text{ TitleFont = render.Text{
FontFilename: "./fonts/DejaVuSans-Bold.ttf", FontFilename: "DejaVuSans-Bold.ttf",
Size: 12, Size: 12,
Padding: 4, Padding: 4,
Color: render.White, Color: render.White,
@ -47,7 +47,7 @@ var (
// LabelFont is the font for strong labels in UI. // LabelFont is the font for strong labels in UI.
LabelFont = render.Text{ LabelFont = render.Text{
Size: 12, Size: 12,
FontFilename: "./fonts/DejaVuSans-Bold.ttf", FontFilename: "DejaVuSans-Bold.ttf",
Padding: 4, Padding: 4,
Color: render.Black, Color: render.Black,
} }
@ -56,7 +56,7 @@ var (
DragColor = render.MustHexColor("#0099FF") DragColor = render.MustHexColor("#0099FF")
PlayButtonFont = render.Text{ PlayButtonFont = render.Text{
FontFilename: "./fonts/DejaVuSans-Bold.ttf", FontFilename: "DejaVuSans-Bold.ttf",
Size: 16, Size: 16,
Padding: 4, Padding: 4,
Color: render.RGBA(255, 255, 0, 255), Color: render.RGBA(255, 255, 0, 255),

View File

@ -46,7 +46,18 @@ func ListDoodads() ([]string, error) {
// Append user doodads. // Append user doodads.
userFiles, err := userdir.ListDoodads() userFiles, err := userdir.ListDoodads()
names = append(names, userFiles...) names = append(names, userFiles...)
return names, err
// Deduplicate names.
var uniq = map[string]interface{}{}
var result []string
for _, name := range names {
if _, ok := uniq[name]; !ok {
uniq[name] = nil
result = append(result, name)
}
}
return result, err
} }
// LoadFile reads a doodad file from disk, checking a few locations. // LoadFile reads a doodad file from disk, checking a few locations.

View File

@ -302,14 +302,16 @@ func (u *EditorUI) SetupCanvas(d *Doodle) *uix.Canvas {
} }
// A link event to connect two actors together. // A link event to connect two actors together.
drawing.OnLinkActors = func(a, b *level.Actor) { drawing.OnLinkActors = func(a, b *uix.Actor) {
d.Flash("Link %s and %s", a.Filename, b.Filename) // The actors are a uix.Actor which houses a level.Actor which we
idA, idB := a.ID(), b.ID() // want to update to map each other's IDs.
a.AddLink(idB) idA, idB := a.Actor.ID(), b.Actor.ID()
b.AddLink(idA) a.Actor.AddLink(idB)
b.Actor.AddLink(idA)
// Reset the Link tool. // Reset the Link tool.
drawing.Tool = uix.ActorTool drawing.Tool = uix.ActorTool
d.Flash("Linked '%s' and '%s' together", a.Doodad.Title, b.Doodad.Title)
} }
// Set up the drop handler for draggable doodads. // Set up the drop handler for draggable doodads.

View File

@ -6,8 +6,10 @@ import (
"git.kirsle.net/apps/doodle/lib/ui" "git.kirsle.net/apps/doodle/lib/ui"
"git.kirsle.net/apps/doodle/pkg/balance" "git.kirsle.net/apps/doodle/pkg/balance"
"git.kirsle.net/apps/doodle/pkg/branding" "git.kirsle.net/apps/doodle/pkg/branding"
"git.kirsle.net/apps/doodle/pkg/filesystem"
"git.kirsle.net/apps/doodle/pkg/level" "git.kirsle.net/apps/doodle/pkg/level"
"git.kirsle.net/apps/doodle/pkg/log" "git.kirsle.net/apps/doodle/pkg/log"
"git.kirsle.net/apps/doodle/pkg/scripting"
"git.kirsle.net/apps/doodle/pkg/uix" "git.kirsle.net/apps/doodle/pkg/uix"
) )
@ -17,7 +19,8 @@ type MainScene struct {
frame *ui.Frame frame *ui.Frame
// Background wallpaper canvas. // Background wallpaper canvas.
canvas *uix.Canvas scripting *scripting.Supervisor
canvas *uix.Canvas
} }
// Name of the scene. // Name of the scene.
@ -29,18 +32,9 @@ func (s *MainScene) Name() string {
func (s *MainScene) Setup(d *Doodle) error { func (s *MainScene) Setup(d *Doodle) error {
s.Supervisor = ui.NewSupervisor() s.Supervisor = ui.NewSupervisor()
// Set up the background wallpaper canvas. if err := s.SetupDemoLevel(d); err != nil {
s.canvas = uix.NewCanvas(100, false) return err
s.canvas.Resize(render.Rect{ }
W: int32(d.width),
H: int32(d.height),
})
s.canvas.LoadLevel(d.Engine, &level.Level{
Chunker: level.NewChunker(100),
Palette: level.NewPalette(),
PageType: level.Bounded,
Wallpaper: "notebook.png",
})
// Main UI button frame. // Main UI button frame.
frame := ui.NewFrame("frame") frame := ui.NewFrame("frame")
@ -78,10 +72,52 @@ func (s *MainScene) Setup(d *Doodle) error {
return nil return nil
} }
// SetupDemoLevel configures the wallpaper behind the New screen,
// which demos a title screen demo level.
func (s *MainScene) SetupDemoLevel(d *Doodle) error {
// Set up the background wallpaper canvas.
s.canvas = uix.NewCanvas(100, false)
s.canvas.Scrollable = true
s.canvas.Resize(render.Rect{
W: int32(d.width),
H: int32(d.height),
})
// Title screen level to load.
lvlName, _ := filesystem.FindFile("example1.level")
lvl, err := level.LoadJSON(lvlName)
if err != nil {
log.Error("Error loading title-screen.level: %s", err)
}
s.canvas.LoadLevel(d.Engine, lvl)
s.canvas.InstallActors(lvl.Actors)
// Load all actor scripts.
s.scripting = scripting.NewSupervisor()
s.canvas.SetScriptSupervisor(s.scripting)
if err := s.scripting.InstallScripts(lvl); err != nil {
log.Error("Error with title screen level scripts: %s", err)
}
// Run all actors scripts main function to start them off.
if err := s.canvas.InstallScripts(); err != nil {
log.Error("Error running actor main() functions: %s", err)
}
return nil
}
// Loop the editor scene. // Loop the editor scene.
func (s *MainScene) Loop(d *Doodle, ev *events.State) error { func (s *MainScene) Loop(d *Doodle, ev *events.State) error {
s.Supervisor.Loop(ev) s.Supervisor.Loop(ev)
if err := s.scripting.Loop(); err != nil {
log.Error("MainScene.Loop: scripting.Loop: %s", err)
}
s.canvas.Loop(ev)
if resized := ev.Resized.Read(); resized { if resized := ev.Resized.Read(); resized {
w, h := d.Engine.WindowSize() w, h := d.Engine.WindowSize()
d.width = w d.width = w
@ -103,6 +139,14 @@ func (s *MainScene) Draw(d *Doodle) error {
s.canvas.Present(d.Engine, render.Origin) s.canvas.Present(d.Engine, render.Origin)
// Draw a sheen over the level for clarity.
d.Engine.DrawBox(render.RGBA(255, 255, 254, 128), render.Rect{
X: 0,
Y: 0,
W: int32(d.width),
H: int32(d.height),
})
label := ui.NewLabel(ui.Label{ label := ui.NewLabel(ui.Label{
Text: branding.AppName, Text: branding.AppName,
Font: render.Text{ Font: render.Text{

View File

@ -10,6 +10,7 @@ import (
"git.kirsle.net/apps/doodle/lib/render" "git.kirsle.net/apps/doodle/lib/render"
"git.kirsle.net/apps/doodle/lib/ui" "git.kirsle.net/apps/doodle/lib/ui"
"git.kirsle.net/apps/doodle/pkg/balance" "git.kirsle.net/apps/doodle/pkg/balance"
"git.kirsle.net/apps/doodle/pkg/bindata"
"git.kirsle.net/apps/doodle/pkg/doodads" "git.kirsle.net/apps/doodle/pkg/doodads"
"git.kirsle.net/apps/doodle/pkg/level" "git.kirsle.net/apps/doodle/pkg/level"
"git.kirsle.net/apps/doodle/pkg/log" "git.kirsle.net/apps/doodle/pkg/log"
@ -69,7 +70,7 @@ type Canvas struct {
// -- WHEN Canvas.Tool is "Link" -- // -- WHEN Canvas.Tool is "Link" --
// When the Canvas wants to link two actors together. Arguments are the IDs // When the Canvas wants to link two actors together. Arguments are the IDs
// of the two actors. // of the two actors.
OnLinkActors func(a, b *level.Actor) OnLinkActors func(a, b *Actor)
linkFirst *Actor linkFirst *Actor
// Tracking pixels while editing. TODO: get rid of pixelHistory? // Tracking pixels while editing. TODO: get rid of pixelHistory?
@ -129,9 +130,12 @@ func (w *Canvas) LoadLevel(e render.Engine, level *level.Level) {
// TODO: wallpaper paths // TODO: wallpaper paths
filename := "assets/wallpapers/" + level.Wallpaper filename := "assets/wallpapers/" + level.Wallpaper
if runtime.GOOS != "js" { if runtime.GOOS != "js" {
if _, err := os.Stat(filename); os.IsNotExist(err) { // Check if the wallpaper wasn't found. Check bindata and file system.
log.Error("LoadLevel: %s", err) if _, err := bindata.Asset(filename); err != nil {
filename = "assets/wallpapers/notebook.png" // XXX TODO if _, err := os.Stat(filename); os.IsNotExist(err) {
log.Error("LoadLevel: wallpaper %s did not appear to exist, default to notebook.png", filename)
filename = "assets/wallpapers/notebook.png"
}
} }
} }

View File

@ -1,6 +1,10 @@
package uix package uix
import "errors" import (
"errors"
"git.kirsle.net/apps/doodle/pkg/shmem"
)
// LinkStart initializes the Link tool. // LinkStart initializes the Link tool.
func (w *Canvas) LinkStart() { func (w *Canvas) LinkStart() {
@ -13,10 +17,13 @@ func (w *Canvas) LinkAdd(a *Actor) error {
if w.linkFirst == nil { if w.linkFirst == nil {
// First click, hold onto this actor. // First click, hold onto this actor.
w.linkFirst = a w.linkFirst = a
shmem.Flash("Doodad '%s' selected, click the next Doodad to link it to",
a.Doodad.Title,
)
} else { } else {
// Second click, call the OnLinkActors handler with the two actors. // Second click, call the OnLinkActors handler with the two actors.
if w.OnLinkActors != nil { if w.OnLinkActors != nil {
w.OnLinkActors(w.linkFirst.Actor, a.Actor) w.OnLinkActors(w.linkFirst, a)
} else { } else {
return errors.New("Canvas.LinkAdd: no OnLinkActors handler is ready") return errors.New("Canvas.LinkAdd: no OnLinkActors handler is ready")
} }

View File

@ -1,6 +1,7 @@
package wallpaper package wallpaper
import ( import (
"bytes"
"image" "image"
"image/draw" "image/draw"
"os" "os"
@ -9,6 +10,7 @@ import (
"strings" "strings"
"git.kirsle.net/apps/doodle/lib/render" "git.kirsle.net/apps/doodle/lib/render"
"git.kirsle.net/apps/doodle/pkg/bindata"
) )
// Wallpaper is a repeatable background image to go behind levels. // Wallpaper is a repeatable background image to go behind levels.
@ -64,14 +66,28 @@ func FromFile(e render.Engine, filename string) (*Wallpaper, error) {
}, nil }, nil
} }
fh, err := os.Open(filename) // Try and get an image object by any means.
if err != nil { var (
return nil, err img image.Image
format string
imgErr error
)
// Try the bindata store.
if data, err := bindata.Asset(filename); err == nil {
fh := bytes.NewBuffer(data)
img, format, imgErr = image.Decode(fh)
} else {
fh, err := os.Open(filename)
if err != nil {
return nil, err
}
img, format, imgErr = image.Decode(fh)
} }
img, format, err := image.Decode(fh) // Image loading error?
if err != nil { if imgErr != nil {
return nil, err return nil, imgErr
} }
// Ugly hack: make it an image.RGBA because the thing we get tends to be // Ugly hack: make it an image.RGBA because the thing we get tends to be

View File

@ -20,7 +20,6 @@ func main() {
go watchChanges() go watchChanges()
http.Handle("/", http.FileServer(http.Dir("."))) http.Handle("/", http.FileServer(http.Dir(".")))
http.Handle("/fonts", http.FileServer(http.Dir("../fonts/")))
http.HandleFunc(wasm, func(w http.ResponseWriter, r *http.Request) { http.HandleFunc(wasm, func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/wasm") w.Header().Set("Content-Type", "application/wasm")
http.ServeFile(w, r, "."+wasm) http.ServeFile(w, r, "."+wasm)