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.
This commit is contained in:
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.
.PHONY: 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.
.PHONY: 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.
.PHONY: wasm
@ -102,7 +102,7 @@ test:
dist: doodads bindata build
mkdir -p 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 && zip -r doodle-$(VERSION).zip doodle-$(VERSION)

View File

@ -11,6 +11,7 @@ import (
"git.kirsle.net/apps/doodle/lib/render/sdl"
doodle "git.kirsle.net/apps/doodle/pkg"
"git.kirsle.net/apps/doodle/pkg/balance"
"git.kirsle.net/apps/doodle/pkg/bindata"
"git.kirsle.net/apps/doodle/pkg/branding"
"github.com/urfave/cli"
@ -80,6 +81,20 @@ func main() {
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.SetupEngine()
if c.Bool("guitest") {

View File

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

View File

@ -3,6 +3,7 @@ package sdl
import (
"fmt"
"strings"
"sync"
"git.kirsle.net/apps/doodle/lib/events"
"git.kirsle.net/apps/doodle/lib/render"
@ -11,9 +12,29 @@ import (
)
// 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.
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
}
font, err := ttf.OpenFont(filename, size)
// Do we have this font in memory?
var (
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, err
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
return font, nil

View File

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

View File

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

View File

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

View File

@ -46,7 +46,18 @@ func ListDoodads() ([]string, error) {
// Append user doodads.
userFiles, err := userdir.ListDoodads()
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.

View File

@ -302,14 +302,16 @@ func (u *EditorUI) SetupCanvas(d *Doodle) *uix.Canvas {
}
// A link event to connect two actors together.
drawing.OnLinkActors = func(a, b *level.Actor) {
d.Flash("Link %s and %s", a.Filename, b.Filename)
idA, idB := a.ID(), b.ID()
a.AddLink(idB)
b.AddLink(idA)
drawing.OnLinkActors = func(a, b *uix.Actor) {
// The actors are a uix.Actor which houses a level.Actor which we
// want to update to map each other's IDs.
idA, idB := a.Actor.ID(), b.Actor.ID()
a.Actor.AddLink(idB)
b.Actor.AddLink(idA)
// Reset the Link tool.
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.

View File

@ -6,8 +6,10 @@ import (
"git.kirsle.net/apps/doodle/lib/ui"
"git.kirsle.net/apps/doodle/pkg/balance"
"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/log"
"git.kirsle.net/apps/doodle/pkg/scripting"
"git.kirsle.net/apps/doodle/pkg/uix"
)
@ -17,6 +19,7 @@ type MainScene struct {
frame *ui.Frame
// Background wallpaper canvas.
scripting *scripting.Supervisor
canvas *uix.Canvas
}
@ -29,18 +32,9 @@ func (s *MainScene) Name() string {
func (s *MainScene) Setup(d *Doodle) error {
s.Supervisor = ui.NewSupervisor()
// Set up the background wallpaper canvas.
s.canvas = uix.NewCanvas(100, false)
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",
})
if err := s.SetupDemoLevel(d); err != nil {
return err
}
// Main UI button frame.
frame := ui.NewFrame("frame")
@ -78,10 +72,52 @@ func (s *MainScene) Setup(d *Doodle) error {
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.
func (s *MainScene) Loop(d *Doodle, ev *events.State) error {
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 {
w, h := d.Engine.WindowSize()
d.width = w
@ -103,6 +139,14 @@ func (s *MainScene) Draw(d *Doodle) error {
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{
Text: branding.AppName,
Font: render.Text{

View File

@ -10,6 +10,7 @@ import (
"git.kirsle.net/apps/doodle/lib/render"
"git.kirsle.net/apps/doodle/lib/ui"
"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/level"
"git.kirsle.net/apps/doodle/pkg/log"
@ -69,7 +70,7 @@ type Canvas struct {
// -- WHEN Canvas.Tool is "Link" --
// When the Canvas wants to link two actors together. Arguments are the IDs
// of the two actors.
OnLinkActors func(a, b *level.Actor)
OnLinkActors func(a, b *Actor)
linkFirst *Actor
// 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
filename := "assets/wallpapers/" + level.Wallpaper
if runtime.GOOS != "js" {
// Check if the wallpaper wasn't found. Check bindata and file system.
if _, err := bindata.Asset(filename); err != nil {
if _, err := os.Stat(filename); os.IsNotExist(err) {
log.Error("LoadLevel: %s", err)
filename = "assets/wallpapers/notebook.png" // XXX TODO
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
import "errors"
import (
"errors"
"git.kirsle.net/apps/doodle/pkg/shmem"
)
// LinkStart initializes the Link tool.
func (w *Canvas) LinkStart() {
@ -13,10 +17,13 @@ func (w *Canvas) LinkAdd(a *Actor) error {
if w.linkFirst == nil {
// First click, hold onto this actor.
w.linkFirst = a
shmem.Flash("Doodad '%s' selected, click the next Doodad to link it to",
a.Doodad.Title,
)
} else {
// Second click, call the OnLinkActors handler with the two actors.
if w.OnLinkActors != nil {
w.OnLinkActors(w.linkFirst.Actor, a.Actor)
w.OnLinkActors(w.linkFirst, a)
} else {
return errors.New("Canvas.LinkAdd: no OnLinkActors handler is ready")
}

View File

@ -1,6 +1,7 @@
package wallpaper
import (
"bytes"
"image"
"image/draw"
"os"
@ -9,6 +10,7 @@ import (
"strings"
"git.kirsle.net/apps/doodle/lib/render"
"git.kirsle.net/apps/doodle/pkg/bindata"
)
// Wallpaper is a repeatable background image to go behind levels.
@ -64,14 +66,28 @@ func FromFile(e render.Engine, filename string) (*Wallpaper, error) {
}, nil
}
// Try and get an image object by any means.
var (
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)
if err != nil {
return nil, err
// Image loading error?
if imgErr != nil {
return nil, imgErr
}
// 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()
http.Handle("/", http.FileServer(http.Dir(".")))
http.Handle("/fonts", http.FileServer(http.Dir("../fonts/")))
http.HandleFunc(wasm, func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/wasm")
http.ServeFile(w, r, "."+wasm)