diff --git a/assets/pattern/perlin-noise.png b/assets/pattern/perlin-noise.png new file mode 100644 index 0000000..920c0e7 Binary files /dev/null and b/assets/pattern/perlin-noise.png differ diff --git a/pkg/balance/theme.go b/pkg/balance/theme.go index be0ae22..d65cb23 100644 --- a/pkg/balance/theme.go +++ b/pkg/balance/theme.go @@ -29,6 +29,22 @@ var ( Shadow: render.Black, } + // Loading Screen fonts. + LoadScreenFont = render.Text{ + Size: 46, + Color: render.Pink, + Stroke: render.SkyBlue, + Shadow: render.Black, + } + LoadScreenSecondaryFont = render.Text{ + FontFilename: "DejaVuSans.ttf", + Size: 18, + Color: render.SkyBlue, + Shadow: render.SkyBlue.Darken(128), + // Color: render.RGBA(255, 153, 0, 255), + // Shadow: render.RGBA(200, 80, 0, 255), + } + // Window and panel styles. TitleConfig = ui.Config{ Background: render.MustHexColor("#FF9900"), diff --git a/pkg/doodle.go b/pkg/doodle.go index 630180f..c1a9ade 100644 --- a/pkg/doodle.go +++ b/pkg/doodle.go @@ -12,6 +12,7 @@ import ( "git.kirsle.net/apps/doodle/pkg/keybind" "git.kirsle.net/apps/doodle/pkg/log" "git.kirsle.net/apps/doodle/pkg/modal" + "git.kirsle.net/apps/doodle/pkg/modal/loadscreen" "git.kirsle.net/apps/doodle/pkg/native" "git.kirsle.net/apps/doodle/pkg/pattern" "git.kirsle.net/apps/doodle/pkg/shmem" @@ -160,8 +161,9 @@ func (d *Doodle) Run() error { DebugCollision = !DebugCollision } - // Is a UI modal active? - if modal.Handled(ev) == false { + // Make sure no UI modals (alerts, confirms) + // or loadscreen are currently visible. + if !modal.Handled(ev) { // Run the scene's logic. err = d.Scene.Loop(d, ev) if err != nil { @@ -174,6 +176,9 @@ func (d *Doodle) Run() error { // Draw the scene. d.Scene.Draw(d) + // Draw the loadscreen if it is active. + loadscreen.Loop(render.NewRect(d.width, d.height), d.Engine) + // Draw modals on top of the game UI. modal.Draw() diff --git a/pkg/editor_scene.go b/pkg/editor_scene.go index 9d7d989..cd06bd3 100644 --- a/pkg/editor_scene.go +++ b/pkg/editor_scene.go @@ -15,6 +15,7 @@ import ( "git.kirsle.net/apps/doodle/pkg/license" "git.kirsle.net/apps/doodle/pkg/log" "git.kirsle.net/apps/doodle/pkg/modal" + "git.kirsle.net/apps/doodle/pkg/modal/loadscreen" "git.kirsle.net/apps/doodle/pkg/usercfg" "git.kirsle.net/apps/doodle/pkg/userdir" "git.kirsle.net/go/render" @@ -64,6 +65,21 @@ func (s *EditorScene) Setup(d *Doodle) error { {"Swatch:", s.debSwatch}, } + // Show the loading screen. + loadscreen.ShowWithProgress() + go func() { + if err := s.setupAsync(d); err != nil { + log.Error("EditorScene.setupAsync: %s", err) + } + loadscreen.Hide() + }() + + return nil +} + +// setupAsync initializes trhe editor scene in the background, +// underneath a loading screen. +func (s *EditorScene) setupAsync(d *Doodle) error { // Initialize the user interface. It references the palette and such so it // must be initialized after those things. s.d = d @@ -81,10 +97,17 @@ func (s *EditorScene) Setup(d *Doodle) error { case enum.LevelDrawing: if s.Level != nil { log.Debug("EditorScene.Setup: received level from scene caller") + loadscreen.SetSubtitle( + "Opening: "+s.Level.Title, + "by "+s.Level.Author, + ) s.UI.Canvas.LoadLevel(d.Engine, s.Level) s.UI.Canvas.InstallActors(s.Level.Actors) } else if s.filename != "" && s.OpenFile { log.Debug("EditorScene.Setup: Loading map from filename at %s", s.filename) + loadscreen.SetSubtitle( + "Opening: " + s.filename, + ) if err := s.LoadLevel(s.filename); err != nil { d.Flash("LoadLevel error: %s", err) } else { @@ -113,6 +136,12 @@ func (s *EditorScene) Setup(d *Doodle) error { s.UI.Canvas.ScrollTo(render.Origin) s.UI.Canvas.Scrollable = true } + + // Update the loading screen with level info. + loadscreen.SetSubtitle( + "Opening: "+s.Level.Title, + "by "+s.Level.Author, + ) case enum.DoodadDrawing: // Getting a doodad from file? if s.filename != "" && s.OpenFile { @@ -140,6 +169,12 @@ func (s *EditorScene) Setup(d *Doodle) error { s.UI.Canvas.LoadDoodad(s.Doodad) } + // Update the loading screen with level info. + loadscreen.SetSubtitle( + s.Doodad.Title, + "by "+s.Doodad.Author, + ) + // TODO: move inside the UI. Just an approximate position for now. s.UI.Canvas.Resize(render.NewRect(s.DoodadSize, s.DoodadSize)) s.UI.Canvas.ScrollTo(render.Origin) @@ -147,10 +182,20 @@ func (s *EditorScene) Setup(d *Doodle) error { s.UI.Workspace.Compute(d.Engine) } + // Pre-cache all bitmap images from the level chunks. + // Note: we are not running on the main thread, so SDL2 Textures + // don't get created yet, but we do the full work of caching bitmap + // images which later get fed directly into SDL2 saving speed at + // runtime, + the bitmap generation is pretty wicked fast anyway. + loadscreen.PreloadAllChunkBitmaps(s.UI.Canvas.Chunker()) + // Recompute the UI Palette window for the level's palette. s.UI.FinishSetup(d) - d.Flash("Editor Mode. Press 'P' to play this map.") + d.Flash("Editor Mode.") + if s.DrawingType == enum.LevelDrawing { + d.Flash("Press 'P' to playtest this level.") + } return nil } @@ -180,6 +225,11 @@ func (s *EditorScene) ConfirmUnload(fn func()) { // Loop the editor scene. func (s *EditorScene) Loop(d *Doodle, ev *event.State) error { + // Skip if still loading. + if loadscreen.IsActive() { + return nil + } + // Update debug overlay values. *s.debTool = s.UI.Canvas.Tool.String() *s.debSwatch = "???" @@ -253,7 +303,7 @@ func (s *EditorScene) Loop(d *Doodle, ev *event.State) error { // s.UI.Loop(ev) // Switching to Play Mode? - if keybind.GotoPlay(ev) { + if s.DrawingType == enum.LevelDrawing && keybind.GotoPlay(ev) { s.Playtest() } else if keybind.LineTool(ev) { d.Flash("Line Tool selected.") @@ -286,6 +336,11 @@ func (s *EditorScene) Loop(d *Doodle, ev *event.State) error { // Draw the current frame. func (s *EditorScene) Draw(d *Doodle) error { + // Skip if still loading. + if loadscreen.IsActive() { + return nil + } + // Clear the canvas and fill it with magenta so it's clear if any spots are missed. d.Engine.Clear(render.RGBA(160, 120, 160, 255)) diff --git a/pkg/editor_ui.go b/pkg/editor_ui.go index 0016810..1609089 100644 --- a/pkg/editor_ui.go +++ b/pkg/editor_ui.go @@ -107,15 +107,18 @@ func NewEditorUI(d *Doodle, s *EditorScene) *EditorUI { FillX: true, }) - u.PlayButton = ui.NewButton("Play", ui.NewLabel(ui.Label{ - Text: "Play (P)", - Font: balance.PlayButtonFont, - })) - u.PlayButton.Handle(ui.Click, func(ed ui.EventData) error { - u.Scene.Playtest() - return nil - }) - u.Supervisor.Add(u.PlayButton) + // Play Button, for levels only. + if s.DrawingType == enum.LevelDrawing { + u.PlayButton = ui.NewButton("Play", ui.NewLabel(ui.Label{ + Text: "Play (P)", + Font: balance.PlayButtonFont, + })) + u.PlayButton.Handle(ui.Click, func(ed ui.EventData) error { + u.Scene.Playtest() + return nil + }) + u.Supervisor.Add(u.PlayButton) + } // Position the Canvas inside the frame. u.Workspace.Pack(u.Canvas, ui.Pack{ @@ -234,7 +237,7 @@ func (u *EditorUI) Resized(d *Doodle) { } // Position the Play button over the workspace. - { + if u.PlayButton != nil { btn := u.PlayButton btn.Compute(d.Engine) @@ -330,7 +333,9 @@ func (u *EditorUI) Present(e render.Engine) { u.MenuBar.Present(e, u.MenuBar.Point()) u.StatusBar.Present(e, u.StatusBar.Point()) u.ToolBar.Present(e, u.ToolBar.Point()) - u.PlayButton.Present(e, u.PlayButton.Point()) + if u.PlayButton != nil { + u.PlayButton.Present(e, u.PlayButton.Point()) + } u.screen.Present(e, render.Origin) diff --git a/pkg/level/chunk.go b/pkg/level/chunk.go index c4aa27e..f348d7e 100644 --- a/pkg/level/chunk.go +++ b/pkg/level/chunk.go @@ -32,6 +32,7 @@ type Chunk struct { // Texture cache properties so we don't redraw pixel-by-pixel every frame. uuid uuid.UUID + bitmap image.Image texture render.Texturer textureMasked render.Texturer textureMaskedColor render.Color @@ -78,7 +79,7 @@ func NewChunk() *Chunk { func (c *Chunk) Texture(e render.Engine) render.Texturer { if c.texture == nil || c.dirty { // Generate the normal bitmap and one with a color mask if applicable. - tex, err := c.toBitmap(render.Invisible) + tex, err := c.generateTexture(render.Invisible) if err != nil { log.Error("Texture: %s", err) } @@ -93,8 +94,9 @@ func (c *Chunk) Texture(e render.Engine) render.Texturer { // TextureMasked returns a cached texture with the ColorMask applied. func (c *Chunk) TextureMasked(e render.Engine, mask render.Color) render.Texturer { if c.textureMasked == nil || c.textureMaskedColor != mask { - // Generate the normal bitmap and one with a color mask if applicable. - tex, err := c.toBitmap(mask) + // Force regenerate with the new mask color. + c.dirty = true + tex, err := c.generateTexture(mask) if err != nil { log.Error("Texture: %s", err) } @@ -111,8 +113,24 @@ func (c *Chunk) SetDirty() { c.dirty = true } -// toBitmap puts the texture in a well named bitmap path in the cache folder. -func (c *Chunk) toBitmap(mask render.Color) (render.Texturer, error) { +// CachedBitmap returns a cached render of the chunk as a bitmap image. +// +// This is like Texture() but skips the step of actually producing an +// (SDL2) texture. The benefit of this is that you can call it from +// your non-main threads and offload the bitmap work into background +// tasks, then when SDL2 needs the Texture, the cached bitmap is +// immediately there saving time on the main thread. +func (c *Chunk) CachedBitmap(mask render.Color) image.Image { + if c.bitmap == nil || c.dirty { + c.bitmap = c.ToBitmap(mask) + } + return c.bitmap +} + +// generateTexture takes the chunk's Bitmap, turns it into an (SDL2) +// texture, and caches the texture in memory until the chunk is marked +// as dirty. +func (c *Chunk) generateTexture(mask render.Color) (render.Texturer, error) { // Generate a unique name for this chunk cache. var name string if c.uuid == uuid.Nil { @@ -126,12 +144,21 @@ func (c *Chunk) toBitmap(mask render.Color) (render.Texturer, error) { ) } - // Get the temp bitmap image. - return c.ToBitmap(name, mask) + // Get (and/or cache) the chunk to a bitmap image. + // Note: the 1st call to Bitmap or after SetDirty will + // generate the image and store it cached. + bitmap := c.CachedBitmap(mask) + + // Cache the texture data with the current renderer. + tex, err := shmem.CurrentRenderEngine.StoreTexture(name, bitmap) + return tex, err } // ToBitmap exports the chunk's pixels as a bitmap image. -func (c *Chunk) ToBitmap(filename string, mask render.Color) (render.Texturer, error) { +// NOT CACHED! This will always run the logic. Use Bitmap() if you +// want a cached bitmap image that only generates itself once, and +// again when marked dirty. +func (c *Chunk) ToBitmap(mask render.Color) image.Image { canvas := c.SizePositive() imgSize := image.Rectangle{ Min: image.Point{}, @@ -189,9 +216,7 @@ func (c *Chunk) ToBitmap(filename string, mask render.Color) (render.Texturer, e ) } - // Cache the texture data with the current renderer. - tex, err := shmem.CurrentRenderEngine.StoreTexture(filename, img) - return tex, err + return img } // Set proxies to the accessor and flags the texture as dirty. diff --git a/pkg/level/chunker.go b/pkg/level/chunker.go index 648fcee..0d5ebed 100644 --- a/pkg/level/chunker.go +++ b/pkg/level/chunker.go @@ -188,6 +188,43 @@ func (c *Chunker) Redraw() { } } +// Prerender visits every chunk and fetches its texture, in order to pre-load +// the whole drawing for smooth gameplay rather than chunks lazy rendering as +// they enter the screen. +func (c *Chunker) Prerender() { + for _, chunk := range c.Chunks { + _ = chunk.CachedBitmap(render.Invisible) + } +} + +// PrerenderN will pre-render the texture for N number of chunks and then +// yield back to the caller. Returns the number of chunks that still need +// textures rendered; zero when the last chunk has been prerendered. +func (c *Chunker) PrerenderN(n int) (remaining int) { + var ( + total int // total no. of chunks available + totalRendered int // no. of chunks with textures + modified int // number modified this call + ) + + for _, chunk := range c.Chunks { + total++ + if chunk.bitmap != nil { + totalRendered++ + continue + } + + if modified < n { + _ = chunk.CachedBitmap(render.Invisible) + totalRendered++ + modified++ + } + } + + remaining = total - totalRendered + return +} + // Get a pixel at the given coordinate. Returns the Palette entry for that // pixel or else returns an error if not found. func (c *Chunker) Get(p render.Point) (*Swatch, error) { diff --git a/pkg/level/palette_defaults.go b/pkg/level/palette_defaults.go index b8b3c42..d88fc02 100644 --- a/pkg/level/palette_defaults.go +++ b/pkg/level/palette_defaults.go @@ -61,6 +61,12 @@ var ( Solid: true, Pattern: "noise.png", }, + { + Name: "sandstone", + Color: render.RGBA(215, 114, 44, 255), + Solid: true, + Pattern: "perlin-noise.png", + }, { Name: "fire", Color: render.Red, diff --git a/pkg/modal/loadscreen/loadscreen.go b/pkg/modal/loadscreen/loadscreen.go new file mode 100644 index 0000000..6a700f3 --- /dev/null +++ b/pkg/modal/loadscreen/loadscreen.go @@ -0,0 +1,231 @@ +// Package loadscreen implements a modal "Loading" screen for the game, which +// can be shown or hidden by gameplay scenes as needed. +package loadscreen + +import ( + "strings" + + "git.kirsle.net/apps/doodle/pkg/balance" + "git.kirsle.net/apps/doodle/pkg/level" + "git.kirsle.net/apps/doodle/pkg/shmem" + "git.kirsle.net/apps/doodle/pkg/uix" + "git.kirsle.net/go/render" + "git.kirsle.net/go/ui" +) + +// Configuration values. +const ( + ProgressWidth = 300 + ProgressHeight = 34 +) + +// State variables for the loading screen. +var ( + visible bool + withProgress bool + subtitle string // custom subtitle text, SetSubtitle(). + + // Animated title bar + titleBase = "Loading" + animState = 0 + animation = []string{ + ". ", + ".. ", + "...", + " ..", + " .", + " ", + } + animSpeed uint64 = 32 + titleVar string + + // UI widgets. + window *ui.Frame + canvas *uix.Canvas + secondary *ui.Label // subtitle text + progressTrough *ui.Frame + progressBar *ui.Frame + progressText *ui.Label +) + +// Show the basic loading screen without a progress bar. +func Show() { + setup() + visible = true + withProgress = false + subtitle = "" +} + +// ShowWithProgress initializes the loading screen with a progress bar starting at zero. +func ShowWithProgress() { + setup() + visible = true + withProgress = true + subtitle = "" + SetProgress(0) +} + +// SetSubtitle specifies secondary text beneath the Loading banner. +// The subtitle is blanked on Show() and ShowWithProgress() and must +// be specified by the caller if desired. Pass multiple values for +// multiple lines of text. +func SetSubtitle(value ...string) { + subtitle = strings.Join(value, "\n") +} + +// IsActive returns whether the loading screen is currently visible. +func IsActive() bool { + return visible +} + +// Hide the loading screen. +func Hide() { + visible = false +} + +// SetProgress sets the current progress value for loading screens having a progress bar. +func SetProgress(v float64) { + // Resize the progress bar in the trough. + if progressTrough != nil { + var ( + troughSize = progressTrough.Size() + height = progressBar.Size().H + ) + progressBar.Resize(render.Rect{ + W: int(float64(troughSize.W-4) * v), + H: height, + }) + } +} + +// Common function to initialize the loading screen. +func setup() { + if window != nil { + return + } + + titleVar = titleBase + animation[animState] + + // Create the parent container that will stretch full screen. + window = ui.NewFrame("Loadscreen Window") + window.SetBackground(render.RGBA(0, 0, 1, 40)) + + // "Loading" text. + label := ui.NewLabel(ui.Label{ + TextVariable: &titleVar, + Font: balance.LoadScreenFont, + }) + label.Compute(shmem.CurrentRenderEngine) + window.Place(label, ui.Place{ + Top: 128, + Center: true, + }) + + // Subtitle text. + secondary = ui.NewLabel(ui.Label{ + TextVariable: &subtitle, + Font: balance.LoadScreenSecondaryFont, + }) + window.Place(secondary, ui.Place{ + Top: 128 + label.Size().H + 64, + Center: true, + }) + + // Progress bar. + progressTrough = ui.NewFrame("Progress Trough") + progressTrough.Configure(ui.Config{ + Width: ProgressWidth, + Height: ProgressHeight, + BorderSize: 2, + BorderStyle: ui.BorderSunken, + Background: render.DarkGrey, + }) + window.Place(progressTrough, ui.Place{ + Center: true, + Middle: true, + }) + + progressBar = ui.NewFrame("Progress Bar") + progressBar.Configure(ui.Config{ + Width: 0, + Height: ProgressHeight - 4, + Background: render.Green, + }) + progressTrough.Pack(progressBar, ui.Pack{ + Side: ui.W, + }) +} + +// Loop is called on every game loop. If the loadscreen is not active, nothing happens. +// Otherwise the loading screen UI is drawn to screen. +func Loop(windowSize render.Rect, e render.Engine) { + if !visible { + return + } + + if window != nil { + // Initialize the wallpaper canvas? + if canvas == nil { + canvas = uix.NewCanvas(128, false) + canvas.LoadLevel(e, &level.Level{ + Chunker: level.NewChunker(100), + Palette: level.NewPalette(), + PageType: level.Bounded, + Wallpaper: "blueprint.png", + }) + } + canvas.Resize(windowSize) + canvas.Compute(e) + canvas.Present(e, render.Origin) + + window.Resize(windowSize) + window.Compute(e) + window.Present(e, render.Origin) + + // Show/hide the progress bar. + progressTrough.Compute(e) + if withProgress && progressTrough.Hidden() { + progressTrough.Show() + } else if !withProgress && !progressTrough.Hidden() { + progressTrough.Hide() + } + + // Show/hide the subtitle text. + if len(subtitle) > 0 && secondary.Hidden() { + secondary.Show() + } else if subtitle == "" && !secondary.Hidden() { + secondary.Hide() + } + + // Animate the ellipses. + if shmem.Tick%animSpeed == 0 { + titleVar = titleBase + animation[animState] + animState++ + if animState >= len(animation) { + animState = 0 + } + } + + } +} + +// PreloadAllChunkBitmaps is a helper function to eager cache all bitmap +// images of the chunks in a level drawing. It is designed to work with the +// loading screen and will set the Progress percent based on the total number +// of chunks vs. chunks remaining to pre-cache bitmaps from. +func PreloadAllChunkBitmaps(chunker *level.Chunker) { + loadChunksTarget := len(chunker.Chunks) + for { + remaining := chunker.PrerenderN(10) + + // Set the load screen progress % based on number of chunks to render. + if loadChunksTarget > 0 { + percent := float64(loadChunksTarget-remaining) / float64(loadChunksTarget) + SetProgress(percent) + } + + if remaining == 0 { + break + } + } +} diff --git a/pkg/modal/modal.go b/pkg/modal/modal.go index 3959f8b..3188c7a 100644 --- a/pkg/modal/modal.go +++ b/pkg/modal/modal.go @@ -4,6 +4,7 @@ package modal import ( "git.kirsle.net/apps/doodle/pkg/balance" "git.kirsle.net/apps/doodle/pkg/keybind" + "git.kirsle.net/apps/doodle/pkg/modal/loadscreen" "git.kirsle.net/go/render" "git.kirsle.net/go/render/event" "git.kirsle.net/go/ui" @@ -44,7 +45,16 @@ func Reset() { // Handled runs the modal manager's logic. Returns true if a modal // is presently active, to signal to Doodle not to run game logic. +// +// This function also returns true if the pkg/modal/loadscreen is +// currently active. func Handled(ev *event.State) bool { + // The loadscreen counts as a modal for this purpose. + if loadscreen.IsActive() { + return true + } + + // Check if we have a modal currently active. if !ready || current == nil { return false } diff --git a/pkg/pattern/pattern.go b/pkg/pattern/pattern.go index e58ca17..c8cadbc 100644 --- a/pkg/pattern/pattern.go +++ b/pkg/pattern/pattern.go @@ -37,7 +37,11 @@ var Builtins = []Pattern{ Filename: "ink.png", }, { - Name: "Dashed Lines", + Name: "Perlin Noise", + Filename: "perlin-noise.png", + }, + { + Name: "Bubbles", Filename: "circles.png", }, { diff --git a/pkg/play_scene.go b/pkg/play_scene.go index b3f0a52..f787bbd 100644 --- a/pkg/play_scene.go +++ b/pkg/play_scene.go @@ -9,6 +9,7 @@ import ( "git.kirsle.net/apps/doodle/pkg/keybind" "git.kirsle.net/apps/doodle/pkg/level" "git.kirsle.net/apps/doodle/pkg/log" + "git.kirsle.net/apps/doodle/pkg/modal/loadscreen" "git.kirsle.net/apps/doodle/pkg/physics" "git.kirsle.net/apps/doodle/pkg/scripting" "git.kirsle.net/apps/doodle/pkg/uix" @@ -75,6 +76,23 @@ func (s *PlayScene) Setup(d *Doodle) error { s.scripting = scripting.NewSupervisor() s.supervisor = ui.NewSupervisor() + // Show the loading screen. + loadscreen.ShowWithProgress() + go func() { + if err := s.setupAsync(d); err != nil { + log.Error("PlayScene.setupAsync: %s", err) + return + } + + loadscreen.Hide() + }() + + return nil +} + +// setupAsync initializes the play screen in the background, underneath +// a Loading screen. +func (s *PlayScene) setupAsync(d *Doodle) error { // Create an invisible 'screen' frame for UI elements to use for positioning. s.screen = ui.NewFrame("Screen") s.screen.Resize(render.NewRect(d.width, d.height)) @@ -156,6 +174,7 @@ func (s *PlayScene) Setup(d *Doodle) error { s.drawing.LoadLevel(d.Engine, s.Level) s.drawing.InstallActors(s.Level.Actors) } else if s.Filename != "" { + loadscreen.SetSubtitle("Opening: " + s.Filename) log.Debug("PlayScene.Setup: loading map from file %s", s.Filename) // NOTE: s.LoadLevel also calls s.drawing.InstallActors s.LoadLevel(s.Filename) @@ -168,6 +187,12 @@ func (s *PlayScene) Setup(d *Doodle) error { s.drawing.InstallActors(s.Level.Actors) } + // Set the loading screen text with the level metadata. + loadscreen.SetSubtitle( + s.Level.Title, + "by "+s.Level.Author, + ) + // Load all actor scripts. s.drawing.SetScriptSupervisor(s.scripting) if err := s.scripting.InstallScripts(s.Level); err != nil { @@ -188,6 +213,13 @@ func (s *PlayScene) Setup(d *Doodle) error { d.Flash("%s", s.Level.Title) } + // Pre-cache all bitmap images from the level chunks. + // Note: we are not running on the main thread, so SDL2 Textures + // don't get created yet, but we do the full work of caching bitmap + // images which later get fed directly into SDL2 saving speed at + // runtime, + the bitmap generation is pretty wicked fast anyway. + loadscreen.PreloadAllChunkBitmaps(s.Level.Chunker) + s.running = true return nil @@ -374,6 +406,11 @@ func (s *PlayScene) DieByFire(name string) { // Loop the editor scene. func (s *PlayScene) Loop(d *Doodle, ev *event.State) error { + // Skip if still loading. + if loadscreen.IsActive() { + return nil + } + // Update debug overlay values. *s.debWorldIndex = s.drawing.WorldIndexAt(render.NewPoint(ev.CursorX, ev.CursorY)).String() *s.debPosition = s.Player.Position().String() + " vel " + s.Player.Velocity().String() @@ -420,6 +457,11 @@ func (s *PlayScene) Loop(d *Doodle, ev *event.State) error { // Draw the pixels on this frame. func (s *PlayScene) Draw(d *Doodle) error { + // Skip if still loading. + if loadscreen.IsActive() { + return nil + } + // Clear the canvas and fill it with white. d.Engine.Clear(render.White) diff --git a/pkg/shell.go b/pkg/shell.go index b2013cb..22117a0 100644 --- a/pkg/shell.go +++ b/pkg/shell.go @@ -8,6 +8,7 @@ import ( "git.kirsle.net/apps/doodle/pkg/balance" "git.kirsle.net/apps/doodle/pkg/keybind" "git.kirsle.net/apps/doodle/pkg/log" + "git.kirsle.net/apps/doodle/pkg/modal/loadscreen" "git.kirsle.net/apps/doodle/pkg/shmem" "git.kirsle.net/go/render" "git.kirsle.net/go/render/event" @@ -85,6 +86,13 @@ func NewShell(d *Doodle) Shell { } return "" }, + "loadscreen": map[string]interface{}{ + "Show": loadscreen.Show, + "ShowWithProgress": loadscreen.ShowWithProgress, + "Hide": loadscreen.Hide, + "IsActive": loadscreen.IsActive, + "SetProgress": loadscreen.SetProgress, + }, } for name, v := range bindings { err := s.js.Set(name, v)