Level Exit Doodad

* Add a Level Exit doodad, which for now is a little blue flag on a pole
  that reads "END"
* JavaScript API: global function EndLevel() will end the level. The
  exit doodad calls this when touched by the player.
* Add a "Level Completed" alert box UI to PlayScene with dynamic button
  layouts.
  * The alert box pops up when a doodad calls EndLevel() and contains
    action buttons what to do next.
  * "Play Again" restarts the current level again.
  * "Edit Level" if you came from the EditorScene; otherwise this button
    is not visible.
  * "Next Level" is a to-be-implemented button to advance in the single
    player story mode. Only shows up when PlayScene.HasNext=true.
  * "Exit to Menu" is always visible and closes out to the MainScene.
This commit is contained in:
Noah 2019-07-02 15:24:46 -07:00
parent 3d08291bc5
commit 0c22ecae5e
9 changed files with 205 additions and 18 deletions

View File

@ -86,7 +86,19 @@ azulians() {
cd .. cd ..
} }
objects() {
cd objects/
doodad convert -t "Exit Flag" exit-flag.png exit-flag.doodad
doodad install-script exit-flag.js exit-flag.doodad
cp *.doodad ../../../assets/doodads/
cd ..
}
buttons buttons
doors doors
trapdoors trapdoors
azulians azulians
objects

View File

@ -0,0 +1,11 @@
// Exit Flag.
function main() {
console.log("%s initialized!", Self.Doodad.Title);
Self.SetHitbox(22+16, 16, 75-16, 86);
Events.OnCollide(function(e) {
if (e.InHitbox) {
EndLevel();
}
});
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 932 B

View File

@ -2,6 +2,7 @@ package doodle
import ( import (
"fmt" "fmt"
"path/filepath"
"strings" "strings"
"time" "time"
@ -223,11 +224,7 @@ func (d *Doodle) NewDoodad(size int) {
// EditDrawing loads a drawing (Level or Doodad) in Edit Mode. // EditDrawing loads a drawing (Level or Doodad) in Edit Mode.
func (d *Doodle) EditDrawing(filename string) error { func (d *Doodle) EditDrawing(filename string) error {
log.Info("Loading drawing from file: %s", filename) log.Info("Loading drawing from file: %s", filename)
parts := strings.Split(filename, ".") ext := strings.ToLower(filepath.Ext(filename))
if len(parts) < 2 {
return fmt.Errorf("filename `%s` has no file extension", filename)
}
ext := strings.ToLower(parts[len(parts)-1])
scene := &EditorScene{ scene := &EditorScene{
Filename: filename, Filename: filename,
@ -235,11 +232,11 @@ func (d *Doodle) EditDrawing(filename string) error {
} }
switch ext { switch ext {
case "level": case ".level":
case "map": case ".map":
log.Info("is a Level type") log.Info("is a Level type")
scene.DrawingType = enum.LevelDrawing scene.DrawingType = enum.LevelDrawing
case "doodad": case ".doodad":
if balance.FreeVersion { if balance.FreeVersion {
return fmt.Errorf("Doodad editor not supported in your version of the game") return fmt.Errorf("Doodad editor not supported in your version of the game")
} }

View File

@ -131,6 +131,7 @@ func (s *EditorScene) Playtest() {
s.d.Goto(&PlayScene{ s.d.Goto(&PlayScene{
Filename: s.filename, Filename: s.filename,
Level: s.Level, Level: s.Level,
CanEdit: true,
}) })
} }
@ -167,7 +168,7 @@ func (s *EditorScene) Loop(d *Doodle, ev *events.State) error {
// Draw the current frame. // Draw the current frame.
func (s *EditorScene) Draw(d *Doodle) error { func (s *EditorScene) Draw(d *Doodle) error {
// Clear the canvas and fill it with magenta so it's clear if any spots are missed. // Clear the canvas and fill it with magenta so it's clear if any spots are missed.
d.Engine.Clear(render.Magenta) d.Engine.Clear(render.RGBA(160, 120, 160, 255))
s.UI.Present(d.Engine) s.UI.Present(d.Engine)

View File

@ -148,6 +148,7 @@ func (u *EditorUI) Resized(d *Doodle) {
// Position the workspace around with the other widgets. // Position the workspace around with the other widgets.
{ {
frame := u.Workspace frame := u.Workspace
frame.MoveTo(render.NewPoint( frame.MoveTo(render.NewPoint(
0, 0,

View File

@ -19,16 +19,27 @@ type PlayScene struct {
// Configuration attributes. // Configuration attributes.
Filename string Filename string
Level *level.Level Level *level.Level
CanEdit bool // i.e. you came from the Editor Mode
HasNext bool // has a next level to load next
// Private variables. // Private variables.
d *Doodle d *Doodle
drawing *uix.Canvas drawing *uix.Canvas
scripting *scripting.Supervisor scripting *scripting.Supervisor
running bool
// UI widgets. // UI widgets.
supervisor *ui.Supervisor supervisor *ui.Supervisor
editButton *ui.Button editButton *ui.Button
// The alert box shows up when the level goal is reached and includes
// buttons what to do next.
alertBox *ui.Window
alertReplayButton *ui.Button // Replay level
alertEditButton *ui.Button // Edit Level
alertNextButton *ui.Button // Next Level
alertExitButton *ui.Button // Exit to menu
// Custom debug labels. // Custom debug labels.
debPosition *string debPosition *string
debViewport *string debViewport *string
@ -50,6 +61,30 @@ func (s *PlayScene) Setup(d *Doodle) error {
s.scripting = scripting.NewSupervisor() s.scripting = scripting.NewSupervisor()
s.supervisor = ui.NewSupervisor() s.supervisor = ui.NewSupervisor()
// Level Exit handler.
s.SetupAlertbox()
s.scripting.OnLevelExit(func() {
d.Flash("Hurray!")
// Pause the simulation.
s.running = false
// Toggle the relevant buttons on.
if s.CanEdit {
s.alertEditButton.Show()
}
if s.HasNext {
s.alertNextButton.Show()
}
// Always-visible buttons.
s.alertReplayButton.Show()
s.alertExitButton.Show()
// Show the alert box.
s.alertBox.Show()
})
// Initialize debug overlay values. // Initialize debug overlay values.
s.debPosition = new(string) s.debPosition = new(string)
s.debViewport = new(string) s.debViewport = new(string)
@ -126,10 +161,90 @@ func (s *PlayScene) Setup(d *Doodle) error {
} }
d.Flash("Entered Play Mode. Press 'E' to edit this map.") d.Flash("Entered Play Mode. Press 'E' to edit this map.")
s.running = true
return nil return nil
} }
// SetupAlertbox configures the alert box UI.
func (s *PlayScene) SetupAlertbox() {
window := ui.NewWindow("Level Completed")
window.Configure(ui.Config{
Width: 320,
Height: 160,
Background: render.Grey,
})
window.Compute(s.d.Engine)
{
frame := ui.NewFrame("Open Drawing Frame")
window.Pack(frame, ui.Pack{
Anchor: ui.N,
Fill: true,
Expand: true,
})
/******************
* Frame for selecting User Levels
******************/
label1 := ui.NewLabel(ui.Label{
Text: "Congratulations on clearing the level!",
Font: balance.LabelFont,
})
frame.Pack(label1, ui.Pack{
Anchor: ui.N,
FillX: true,
PadY: 16,
})
/******************
* Confirm/cancel buttons.
******************/
bottomFrame := ui.NewFrame("Button Frame")
frame.Pack(bottomFrame, ui.Pack{
Anchor: ui.N,
FillX: true,
PadY: 8,
})
// Button factory for the various options.
makeButton := func(text string, handler func()) *ui.Button {
btn := ui.NewButton(text, ui.NewLabel(ui.Label{
Font: balance.LabelFont,
Text: text,
}))
btn.Handle(ui.Click, func(p render.Point) {
handler()
})
bottomFrame.Pack(btn, ui.Pack{
Anchor: ui.W,
PadX: 2,
})
s.supervisor.Add(btn)
btn.Hide() // all buttons hidden by default
return btn
}
s.alertReplayButton = makeButton("Play Again", func() {
s.RestartLevel()
})
s.alertEditButton = makeButton("Edit Level", func() {
s.EditLevel()
})
s.alertNextButton = makeButton("Next Level", func() {
s.d.Flash("Not Implemented")
})
s.alertExitButton = makeButton("Exit to Menu", func() {
s.d.Goto(&MainScene{})
})
}
s.alertBox = window
s.alertBox.Hide()
}
// EditLevel toggles out of Play Mode to edit the level. // EditLevel toggles out of Play Mode to edit the level.
func (s *PlayScene) EditLevel() { func (s *PlayScene) EditLevel() {
log.Info("Edit Mode, Go!") log.Info("Edit Mode, Go!")
@ -139,6 +254,16 @@ func (s *PlayScene) EditLevel() {
}) })
} }
// RestartLevel starts the level over again.
func (s *PlayScene) RestartLevel() {
log.Info("Restart Level")
s.d.Goto(&PlayScene{
Filename: s.Filename,
Level: s.Level,
CanEdit: s.CanEdit,
})
}
// Loop the editor scene. // Loop the editor scene.
func (s *PlayScene) Loop(d *Doodle, ev *events.State) error { func (s *PlayScene) Loop(d *Doodle, ev *events.State) error {
// Update debug overlay values. // Update debug overlay values.
@ -166,6 +291,8 @@ func (s *PlayScene) Loop(d *Doodle, ev *events.State) error {
return nil return nil
} }
// Is the simulation still running?
if s.running {
// Loop the script supervisor so timeouts/intervals can fire in scripts. // Loop the script supervisor so timeouts/intervals can fire in scripts.
if err := s.scripting.Loop(); err != nil { if err := s.scripting.Loop(); err != nil {
log.Error("PlayScene.Loop: scripting.Loop: %s", err) log.Error("PlayScene.Loop: scripting.Loop: %s", err)
@ -175,6 +302,7 @@ func (s *PlayScene) Loop(d *Doodle, ev *events.State) error {
if err := s.drawing.Loop(ev); err != nil { if err := s.drawing.Loop(ev); err != nil {
log.Error("Drawing loop error: %s", err.Error()) log.Error("Drawing loop error: %s", err.Error())
} }
}
return nil return nil
} }
@ -201,6 +329,16 @@ func (s *PlayScene) Draw(d *Doodle) error {
Y: canSize.H - size.H - padding, Y: canSize.H - size.H - padding,
}) })
// Draw the alert box window.
if !s.alertBox.Hidden() {
s.alertBox.Compute(d.Engine)
s.alertBox.MoveTo(render.Point{
X: int32(d.width/2) - (s.alertBox.Size().W / 2),
Y: int32(d.height/2) - (s.alertBox.Size().H / 2),
})
s.alertBox.Present(d.Engine, s.alertBox.Point())
}
return nil return nil
} }
@ -248,14 +386,14 @@ func (s *PlayScene) Drawing() *uix.Canvas {
func (s *PlayScene) LoadLevel(filename string) error { func (s *PlayScene) LoadLevel(filename string) error {
s.Filename = filename s.Filename = filename
level, err := level.LoadJSON(filename) level, err := level.LoadFile(filename)
if err != nil { if err != nil {
return fmt.Errorf("PlayScene.LoadLevel(%s): %s", filename, err) return fmt.Errorf("PlayScene.LoadLevel(%s): %s", filename, err)
} }
s.Level = level s.Level = level
s.drawing.LoadLevel(s.d.Engine, s.Level) s.drawing.LoadLevel(s.d.Engine, s.Level)
// s.drawing.InstallActors(s.Level.Actors) s.drawing.InstallActors(s.Level.Actors)
return nil return nil
} }

View File

@ -15,6 +15,9 @@ import (
// unique ID. // unique ID.
type Supervisor struct { type Supervisor struct {
scripts map[string]*VM scripts map[string]*VM
// Global event handlers.
onLevelExit func()
} }
// NewSupervisor creates a new JavaScript Supervior. // NewSupervisor creates a new JavaScript Supervior.
@ -67,6 +70,7 @@ func (s *Supervisor) AddLevelScript(id string) error {
s.scripts[id] = NewVM(id) s.scripts[id] = NewVM(id)
RegisterPublishHooks(s.scripts[id]) RegisterPublishHooks(s.scripts[id])
RegisterEventHooks(s, s.scripts[id])
if err := s.scripts[id].RegisterLevelHooks(); err != nil { if err := s.scripts[id].RegisterLevelHooks(); err != nil {
return err return err
} }

View File

@ -0,0 +1,23 @@
package scripting
/*
RegisterEventHooks attaches the supervisor level event hooks into a JS VM.
Names registered:
- EndLevel(): for a doodad to exit the level. Panics if the OnLevelExit
handler isn't defined.
*/
func RegisterEventHooks(s *Supervisor, vm *VM) {
vm.Set("EndLevel", func() {
if s.onLevelExit == nil {
panic("JS EndLevel(): no OnLevelExit handler attached to script supervisor")
}
s.onLevelExit()
})
}
// OnLevelExit registers an event hook for when a Level Exit doodad is reached.
func (s *Supervisor) OnLevelExit(handler func()) {
s.onLevelExit = handler
}