Spit and polish
* New doodad: Invisible Warp Door * All warp doors require the player to be grounded (if affected by gravity) to open them. No jumping or falling thru and opening a warp door mid-air! * Title Screen now randomly selects from a couple of levels. * Title Screen: if it fails to load a level it sets up a basic blank level with a wallpaper instead. * New developer shell command: titlescreen <level> Opens the MainScene with a custom user level as the background. * Add Auto-save to the Editor to save your drawing every 5 minutes * Add a MenuBar to the Play Scene for easier navigation to other features of the game. * Doodad JS API: time.Since() now available.
This commit is contained in:
parent
672ee9641a
commit
9a51ac39f9
59
Changes.md
59
Changes.md
|
@ -1,5 +1,64 @@
|
|||
# Changes
|
||||
|
||||
## v0.11.0 (TBD)
|
||||
|
||||
New features:
|
||||
|
||||
* **High scores and level progression:** when playing levels out of
|
||||
a Level Pack, the game will save your progress and high scores on
|
||||
each level you play. See details on how scoring works so far, below.
|
||||
* **Auto-save** for the Editor. Automatically saves your drawing every
|
||||
5 minutes. Look for e.g. the _autosave.level to recover your drawing
|
||||
if the game crashed or exited wrongly!
|
||||
* **Color picker UI:** when asked to choose a color (e.g. for your level
|
||||
palette) a UI window makes picking a color easy! You can still manually
|
||||
enter a hexadecimal color value but this is no longer required!
|
||||
|
||||
Scoring system:
|
||||
|
||||
* The high score on a level is based on how quickly you complete it.
|
||||
A timer is shown in-game and there are two possible high scores
|
||||
for each level in a pack:
|
||||
* Perfect Time (gold): if you complete the level without dying and
|
||||
restarting at a checkpoint.
|
||||
* Best Time (silver): if you had used a checkpoint.
|
||||
* The gold/silver icon is displayed next to the timer in-game; it
|
||||
starts gold and drops to silver if you die and restart from checkpoint.
|
||||
It gives you a preview of which high score you'll be competing with.
|
||||
* If cheat codes are used, the user is not eligible for a high score
|
||||
but may still mark the level "completed."
|
||||
* Level Packs may have some of their later levels locked by default,
|
||||
with only one or a few available immediately. Completing a level will
|
||||
unlock the next level until they have all been unlocked.
|
||||
|
||||
New and changed doodads:
|
||||
|
||||
* **Invisible Warp Door:** a technical doodad to create an invisible
|
||||
Warp Door (press Space/'Use' key to activate).
|
||||
* All **Warp Doors** now require the player to be grounded before they
|
||||
can be opened, or else under the effects of antigravity. You shouldn't
|
||||
be able to open Warp Doors while falling or jumping off the ground
|
||||
at them.
|
||||
|
||||
Revised levels:
|
||||
|
||||
* Desert-2of2.level uses a new work-around for the unfortunate glitch
|
||||
of sometimes getting stuck on two boxes, instead of a cheat code
|
||||
being necessary to resolve.
|
||||
* Revised difficulty on Tutorial 2 and Tutorial 3.
|
||||
|
||||
Miscellaneous changes:
|
||||
|
||||
* Title Screen: picks a random level from a few options, in the future
|
||||
it will pick random user levels too.
|
||||
* Play Mode gets a menu bar like the Editor for easier navigation to
|
||||
other game features.
|
||||
* New dev shell command: `titlescreen <level name>` to load the Title
|
||||
Screen with the named level as its background, can be used to load
|
||||
user levels _now_.
|
||||
* For the doodads JavaScript API: `time.Since()` is now available (from
|
||||
the Go standard library)
|
||||
|
||||
## v0.10.0 (Dec 30 2021)
|
||||
|
||||
New features and changes:
|
||||
|
|
|
@ -23,6 +23,11 @@ build:
|
|||
doodad convert -t "Power Source" power-64.png power-source.doodad
|
||||
doodad install-script power.js power-source.doodad
|
||||
|
||||
# Warp Door
|
||||
doodad convert -t "Invisible Warp Door" warp-door-64.png reg-warp-door.doodad
|
||||
doodad edit-doodad --tag "color=invisible" reg-warp-door.doodad
|
||||
doodad install-script ../warp-door/warp-door.js reg-warp-door.doodad
|
||||
|
||||
for i in *.doodad; do\
|
||||
doodad edit-doodad --tag "category=technical" $${i};\
|
||||
done
|
||||
|
|
BIN
dev-assets/doodads/regions/warp-door-64.png
Normal file
BIN
dev-assets/doodads/regions/warp-door-64.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 821 B |
|
@ -17,7 +17,7 @@ build:
|
|||
doodad install-script warp-door.js warp-door-orange.doodad
|
||||
|
||||
for i in *.doodad; do\
|
||||
doodad edit-doodad --tag "category=doors" $${i};\
|
||||
doodad edit-doodad --tag "category=doors" --hitbox=34,76 $${i};\
|
||||
done
|
||||
for i in warp-door-*.doodad; do\
|
||||
doodad edit-doodad --tag "category=doors,gizmos" $${i};\
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
// Warp Doors
|
||||
function main() {
|
||||
Self.SetHitbox(0, 0, 34, 76);
|
||||
|
||||
// Are we a blue or orange door? Regular warp door will be 'none'
|
||||
var color = Self.GetTag("color");
|
||||
var isStateDoor = color === 'blue' || color === 'orange';
|
||||
|
@ -23,6 +21,11 @@ function main() {
|
|||
Self.AddAnimation("close", animSpeed, ["orange-4", "orange-3", "orange-2", "orange-1"]);
|
||||
spriteDefault = "orange-1";
|
||||
spriteDisabled = "orange-off";
|
||||
} else if (color === 'invisible') {
|
||||
// Invisible Warp Door region.
|
||||
Self.Hide();
|
||||
Self.AddAnimation("open", animSpeed, [0, 0]);
|
||||
Self.AddAnimation("close", animSpeed, [0, 0]);
|
||||
} else {
|
||||
Self.AddAnimation("open", animSpeed, ["door-2", "door-3", "door-4"]);
|
||||
Self.AddAnimation("close", animSpeed, ["door-4", "door-3", "door-2", "door-1"]);
|
||||
|
@ -48,6 +51,10 @@ function main() {
|
|||
});
|
||||
}
|
||||
|
||||
// For player groundedness work-around
|
||||
var playerLastY = []; // last sampling of Y values
|
||||
var lastUsed = time.Now();
|
||||
|
||||
// The player Uses the door.
|
||||
var flashedCooldown = false; // "Locked Door" flashed message.
|
||||
Events.OnUse(function(e) {
|
||||
|
@ -74,6 +81,40 @@ function main() {
|
|||
return;
|
||||
}
|
||||
|
||||
// The player must be grounded or have no gravity to open the door.
|
||||
if (!e.Actor.Grounded() && e.Actor.HasGravity()) {
|
||||
// Work-around: if two Boxes are stacked atop each other the player can
|
||||
// get stuck if he jumps on top. He may not be Grounded but isn't changing
|
||||
// effective Y position and a warp door may work as a good way out.
|
||||
var yValue = e.Actor.Position().Y;
|
||||
|
||||
// Collect a sampling of last few Y values. If the player Y position
|
||||
// is constant the last handful of frames, treat them as if they're
|
||||
// grounded (or else they can't activate the warp door).
|
||||
playerLastY.unshift(yValue);
|
||||
if (playerLastY.length < 6) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We have enough history.
|
||||
playerLastY.pop();
|
||||
|
||||
// Hasn't moved?
|
||||
var isGrounded = true;
|
||||
for (var i = 0; i < playerLastY.length; i++) {
|
||||
if (yValue !== playerLastY[i]) {
|
||||
isGrounded = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isGrounded) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Player was effectively grounded! No change in Y position.
|
||||
}
|
||||
|
||||
// Freeze the player.
|
||||
e.Actor.Freeze()
|
||||
|
||||
|
|
|
@ -59,11 +59,18 @@ var (
|
|||
DefaultEraserBrushSize = 8
|
||||
MaxEraserBrushSize = 32 // the bigger, the slower
|
||||
|
||||
// Interval for auto-save in the editor
|
||||
AutoSaveInterval = 5 * time.Minute
|
||||
|
||||
// Default player character doodad in Play Mode.
|
||||
PlayerCharacterDoodad = "boy.doodad"
|
||||
|
||||
// Level name for the title screen.
|
||||
DemoLevelName = "Tutorial 3.level"
|
||||
// Level names for the title screen.
|
||||
DemoLevelName = []string{
|
||||
"Tutorial 1.level",
|
||||
"Tutorial 2.level",
|
||||
"Tutorial 3.level",
|
||||
}
|
||||
|
||||
// Level attachment filename for the custom wallpaper.
|
||||
// NOTE: due to hard-coded "assets/wallpapers/" prefix in uix/canvas.go#LoadLevel.
|
||||
|
|
|
@ -60,6 +60,8 @@ func (c Command) Run(d *Doodle) error {
|
|||
return c.Play(d)
|
||||
case "close":
|
||||
return c.Close(d)
|
||||
case "titlescreen":
|
||||
return c.TitleScreen(d)
|
||||
case "exit":
|
||||
case "quit":
|
||||
return c.Quit()
|
||||
|
@ -192,6 +194,9 @@ func (c Command) Help(d *Doodle) error {
|
|||
d.Flash("Enter a JavaScript shell on the in-game interpreter")
|
||||
case "boolprop":
|
||||
d.Flash("Toggle boolean values. `boolProp list` lists available")
|
||||
case "titlescreen":
|
||||
d.Flash("Usage: titlescreen <filename.level>")
|
||||
d.Flash("Open the title screen with a level")
|
||||
case "help":
|
||||
d.Flash("Usage: help <command>")
|
||||
d.Flash("Gets further help on a command")
|
||||
|
@ -252,6 +257,20 @@ func (c Command) Play(d *Doodle) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// TitleScreen loads the title with a custom user level.
|
||||
func (c Command) TitleScreen(d *Doodle) error {
|
||||
if len(c.Args) == 0 {
|
||||
return errors.New("Usage: titlescreen <level name.level>")
|
||||
}
|
||||
|
||||
filename := c.Args[0]
|
||||
d.shell.Write("Playing level: " + filename)
|
||||
d.Goto(&MainScene{
|
||||
LevelFilename: filename,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// Quit the command line shell.
|
||||
func (c Command) Quit() error {
|
||||
return nil
|
||||
|
|
44
pkg/common_menubar.go
Normal file
44
pkg/common_menubar.go
Normal file
|
@ -0,0 +1,44 @@
|
|||
package doodle
|
||||
|
||||
import (
|
||||
"git.kirsle.net/apps/doodle/pkg/balance"
|
||||
"git.kirsle.net/apps/doodle/pkg/branding"
|
||||
"git.kirsle.net/apps/doodle/pkg/native"
|
||||
"git.kirsle.net/apps/doodle/pkg/windows"
|
||||
"git.kirsle.net/go/render"
|
||||
"git.kirsle.net/go/ui"
|
||||
)
|
||||
|
||||
// Common menubars between Play and Edit.
|
||||
|
||||
// MakeHelpMenu creates the "Help" menu with its common items
|
||||
// across any scene.
|
||||
func (d *Doodle) MakeHelpMenu(menu *ui.MenuBar, supervisor *ui.Supervisor) *ui.MenuButton {
|
||||
helpMenu := menu.AddMenu("Help")
|
||||
helpMenu.AddItemAccel("User Manual", "F1", func() {
|
||||
native.OpenLocalURL(balance.GuidebookPath)
|
||||
})
|
||||
helpMenu.AddItem("About", func() {
|
||||
aboutWindow := windows.NewAboutWindow(windows.About{
|
||||
Supervisor: supervisor,
|
||||
Engine: d.Engine,
|
||||
})
|
||||
aboutWindow.Compute(d.Engine)
|
||||
aboutWindow.Supervise(supervisor)
|
||||
|
||||
// Center the window.
|
||||
aboutWindow.MoveTo(render.Point{
|
||||
X: (d.width / 2) - (aboutWindow.Size().W / 2),
|
||||
Y: 60,
|
||||
})
|
||||
aboutWindow.Show()
|
||||
})
|
||||
helpMenu.AddSeparator()
|
||||
helpMenu.AddItem("Go to Website", func() {
|
||||
native.OpenURL(branding.Website)
|
||||
})
|
||||
helpMenu.AddItem("Guidebook Online", func() {
|
||||
native.OpenURL(branding.GuidebookURL)
|
||||
})
|
||||
return helpMenu
|
||||
}
|
|
@ -245,6 +245,7 @@ func (d *Doodle) MakeSettingsWindow(supervisor *ui.Supervisor) *ui.Window {
|
|||
CrosshairSize: &usercfg.Current.CrosshairSize,
|
||||
CrosshairColor: &usercfg.Current.CrosshairColor,
|
||||
HideTouchHints: &usercfg.Current.HideTouchHints,
|
||||
DisableAutosave: &usercfg.Current.DisableAutosave,
|
||||
}
|
||||
return windows.MakeSettingsWindow(d.width, d.height, cfg)
|
||||
}
|
||||
|
|
|
@ -5,7 +5,9 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.kirsle.net/apps/doodle/pkg/balance"
|
||||
"git.kirsle.net/apps/doodle/pkg/doodads"
|
||||
"git.kirsle.net/apps/doodle/pkg/drawtool"
|
||||
"git.kirsle.net/apps/doodle/pkg/enum"
|
||||
|
@ -47,6 +49,8 @@ type EditorScene struct {
|
|||
|
||||
// Last saved filename by the user.
|
||||
filename string
|
||||
|
||||
lastAutosaveAt time.Time
|
||||
}
|
||||
|
||||
// Name of the scene.
|
||||
|
@ -66,6 +70,9 @@ func (s *EditorScene) Setup(d *Doodle) error {
|
|||
{"Swatch:", s.debSwatch},
|
||||
}
|
||||
|
||||
// Initialize autosave time.
|
||||
s.lastAutosaveAt = time.Now()
|
||||
|
||||
// Show the loading screen.
|
||||
loadscreen.ShowWithProgress()
|
||||
go func() {
|
||||
|
@ -424,6 +431,16 @@ func (s *EditorScene) Loop(d *Doodle, ev *event.State) error {
|
|||
|
||||
s.UI.Loop(ev)
|
||||
|
||||
// Trigger auto-save of the level in case of crash or accidental closure.
|
||||
if time.Since(s.lastAutosaveAt) > balance.AutoSaveInterval {
|
||||
s.lastAutosaveAt = time.Now()
|
||||
if !usercfg.Current.DisableAutosave {
|
||||
if err := s.AutoSave(); err != nil {
|
||||
d.FlashError("Autosave error: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -471,7 +488,6 @@ func (s *EditorScene) LoadLevel(filename string) error {
|
|||
}
|
||||
|
||||
// SaveLevel saves the level to disk.
|
||||
// TODO: move this into the Canvas?
|
||||
func (s *EditorScene) SaveLevel(filename string) error {
|
||||
if s.DrawingType != enum.LevelDrawing {
|
||||
return errors.New("SaveLevel: current drawing is not a Level type")
|
||||
|
@ -500,6 +516,24 @@ func (s *EditorScene) SaveLevel(filename string) error {
|
|||
return m.WriteFile(filename)
|
||||
}
|
||||
|
||||
// AutoSave takes an autosave snapshot of the level or drawing.
|
||||
func (s *EditorScene) AutoSave() error {
|
||||
var filename = "_autosave.level"
|
||||
|
||||
switch s.DrawingType {
|
||||
case enum.LevelDrawing:
|
||||
s.d.Flash("Automatically saved level to %s", filename)
|
||||
return s.Level.WriteFile(filename)
|
||||
case enum.DoodadDrawing:
|
||||
filename = "_autosave.doodad"
|
||||
|
||||
s.d.Flash("Automatically saved doodad to %s", filename)
|
||||
return s.Doodad.WriteFile(filename)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadDoodad loads a doodad from disk.
|
||||
func (s *EditorScene) LoadDoodad(filename string) error {
|
||||
s.filename = filename
|
||||
|
|
|
@ -6,12 +6,13 @@ package doodle
|
|||
|
||||
import (
|
||||
"git.kirsle.net/apps/doodle/pkg/balance"
|
||||
"git.kirsle.net/apps/doodle/pkg/branding"
|
||||
"git.kirsle.net/apps/doodle/pkg/drawtool"
|
||||
"git.kirsle.net/apps/doodle/pkg/enum"
|
||||
"git.kirsle.net/apps/doodle/pkg/level/giant_screenshot"
|
||||
"git.kirsle.net/apps/doodle/pkg/license"
|
||||
"git.kirsle.net/apps/doodle/pkg/log"
|
||||
"git.kirsle.net/apps/doodle/pkg/native"
|
||||
"git.kirsle.net/apps/doodle/pkg/usercfg"
|
||||
"git.kirsle.net/apps/doodle/pkg/userdir"
|
||||
"git.kirsle.net/apps/doodle/pkg/windows"
|
||||
"git.kirsle.net/go/render"
|
||||
|
@ -143,20 +144,22 @@ func (u *EditorUI) SetupMenuBar(d *Doodle) *ui.MenuBar {
|
|||
native.OpenLocalURL(userdir.ScreenshotDirectory)
|
||||
})
|
||||
|
||||
levelMenu.AddSeparator()
|
||||
levelMenu.AddItemAccel("New viewport", "v", func() {
|
||||
pip := windows.MakePiPWindow(d.width, d.height, windows.PiP{
|
||||
Supervisor: u.Supervisor,
|
||||
Engine: u.d.Engine,
|
||||
Level: u.Scene.Level,
|
||||
Event: u.d.event,
|
||||
if usercfg.Current.EnableFeatures {
|
||||
levelMenu.AddSeparator()
|
||||
levelMenu.AddItemAccel("New viewport", "v", func() {
|
||||
pip := windows.MakePiPWindow(d.width, d.height, windows.PiP{
|
||||
Supervisor: u.Supervisor,
|
||||
Engine: u.d.Engine,
|
||||
Level: u.Scene.Level,
|
||||
Event: u.d.event,
|
||||
|
||||
Tool: &u.Scene.UI.Canvas.Tool,
|
||||
BrushSize: &u.Scene.UI.Canvas.BrushSize,
|
||||
Tool: &u.Scene.UI.Canvas.Tool,
|
||||
BrushSize: &u.Scene.UI.Canvas.BrushSize,
|
||||
})
|
||||
|
||||
pip.Show()
|
||||
})
|
||||
|
||||
pip.Show()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
////////
|
||||
|
@ -261,36 +264,16 @@ func (u *EditorUI) SetupMenuBar(d *Doodle) *ui.MenuBar {
|
|||
|
||||
////////
|
||||
// Help menu
|
||||
helpMenu := menu.AddMenu("Help")
|
||||
helpMenu.AddItemAccel("User Manual", "F1", func() {
|
||||
native.OpenLocalURL(balance.GuidebookPath)
|
||||
})
|
||||
helpMenu.AddItem("Register", func() {
|
||||
u.licenseWindow.Show()
|
||||
})
|
||||
helpMenu.AddItem("About", func() {
|
||||
if u.aboutWindow == nil {
|
||||
u.aboutWindow = windows.NewAboutWindow(windows.About{
|
||||
Supervisor: u.Supervisor,
|
||||
Engine: d.Engine,
|
||||
})
|
||||
u.aboutWindow.Compute(d.Engine)
|
||||
u.aboutWindow.Supervise(u.Supervisor)
|
||||
|
||||
// Center the window.
|
||||
u.aboutWindow.MoveTo(render.Point{
|
||||
X: (d.width / 2) - (u.aboutWindow.Size().W / 2),
|
||||
Y: 60,
|
||||
})
|
||||
}
|
||||
u.aboutWindow.Show()
|
||||
})
|
||||
var (
|
||||
helpMenu = u.d.MakeHelpMenu(menu, u.Supervisor)
|
||||
registerText = "Register"
|
||||
)
|
||||
helpMenu.AddSeparator()
|
||||
helpMenu.AddItem("Go to Website", func() {
|
||||
native.OpenURL(branding.Website)
|
||||
})
|
||||
helpMenu.AddItem("Guidebook Online", func() {
|
||||
native.OpenURL(branding.GuidebookURL)
|
||||
if license.IsRegistered() {
|
||||
registerText = "Registration"
|
||||
}
|
||||
helpMenu.AddItem(registerText, func() {
|
||||
u.licenseWindow.Show()
|
||||
})
|
||||
|
||||
menu.Supervise(u.Supervisor)
|
||||
|
|
|
@ -2,6 +2,7 @@ package doodle
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
|
||||
"git.kirsle.net/apps/doodle/pkg/balance"
|
||||
"git.kirsle.net/apps/doodle/pkg/branding"
|
||||
|
@ -24,7 +25,8 @@ import (
|
|||
|
||||
// MainScene implements the main menu of Doodle.
|
||||
type MainScene struct {
|
||||
Supervisor *ui.Supervisor
|
||||
Supervisor *ui.Supervisor
|
||||
LevelFilename string // custom level filename to load in background
|
||||
|
||||
// Background wallpaper canvas.
|
||||
scripting *scripting.Supervisor
|
||||
|
@ -296,8 +298,16 @@ func (s *MainScene) SetupDemoLevel(d *Doodle) error {
|
|||
s.scripting = scripting.NewSupervisor()
|
||||
s.canvas.SetScriptSupervisor(s.scripting)
|
||||
|
||||
// Title screen level to load.
|
||||
if lvl, err := level.LoadFile(balance.DemoLevelName); err == nil {
|
||||
// Title screen level to load. Pick a random level.
|
||||
levelName := balance.DemoLevelName[0]
|
||||
if s.LevelFilename != "" {
|
||||
levelName = s.LevelFilename
|
||||
} else if len(balance.DemoLevelName) > 1 {
|
||||
randIndex := rand.Intn(len(balance.DemoLevelName))
|
||||
levelName = balance.DemoLevelName[randIndex]
|
||||
}
|
||||
|
||||
if lvl, err := level.LoadFile(levelName); err == nil {
|
||||
s.canvas.LoadLevel(lvl)
|
||||
s.canvas.InstallActors(lvl.Actors)
|
||||
|
||||
|
@ -312,6 +322,16 @@ func (s *MainScene) SetupDemoLevel(d *Doodle) error {
|
|||
}
|
||||
} else {
|
||||
log.Error("Error loading demo level %s: %s", balance.DemoLevelName, err)
|
||||
|
||||
// Create a basic notebook level.
|
||||
s.canvas.LoadLevel(&level.Level{
|
||||
Chunker: level.NewChunker(100),
|
||||
Palette: level.NewPalette(),
|
||||
PageType: level.Bounded,
|
||||
MaxWidth: 42,
|
||||
MaxHeight: 42,
|
||||
Wallpaper: "notebook.png",
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -50,9 +50,11 @@ type PlayScene struct {
|
|||
cheated bool // user has entered a cheat code while playing
|
||||
|
||||
// UI widgets.
|
||||
supervisor *ui.Supervisor
|
||||
screen *ui.Frame // A window sized invisible frame to position UI elements.
|
||||
editButton *ui.Button
|
||||
supervisor *ui.Supervisor
|
||||
screen *ui.Frame // A window sized invisible frame to position UI elements.
|
||||
menubar *ui.MenuBar
|
||||
editButton *ui.Button
|
||||
winLevelPacks *ui.Window
|
||||
|
||||
// Custom debug labels.
|
||||
debPosition *string
|
||||
|
@ -119,6 +121,13 @@ func (s *PlayScene) setupAsync(d *Doodle) error {
|
|||
s.screen = ui.NewFrame("Screen")
|
||||
s.screen.Resize(render.NewRect(d.width, d.height))
|
||||
|
||||
// Menu Bar
|
||||
s.menubar = s.setupMenuBar(d)
|
||||
s.screen.Pack(s.menubar, ui.Pack{
|
||||
Side: ui.N,
|
||||
FillX: true,
|
||||
})
|
||||
|
||||
// Level Exit handler.
|
||||
s.scripting.OnLevelExit(s.BeatLevel)
|
||||
s.scripting.OnLevelFail(s.FailLevel)
|
||||
|
@ -620,6 +629,9 @@ func (s *PlayScene) Draw(d *Doodle) error {
|
|||
// Visualize the touch regions?
|
||||
s.DrawTouchable()
|
||||
|
||||
// Let Supervisor draw menus
|
||||
s.supervisor.Present(d.Engine)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
79
pkg/play_scene_menubar.go
Normal file
79
pkg/play_scene_menubar.go
Normal file
|
@ -0,0 +1,79 @@
|
|||
package doodle
|
||||
|
||||
import (
|
||||
"git.kirsle.net/apps/doodle/pkg/levelpack"
|
||||
"git.kirsle.net/apps/doodle/pkg/shmem"
|
||||
"git.kirsle.net/apps/doodle/pkg/usercfg"
|
||||
"git.kirsle.net/apps/doodle/pkg/windows"
|
||||
"git.kirsle.net/go/render"
|
||||
"git.kirsle.net/go/ui"
|
||||
)
|
||||
|
||||
// Set up the menu bar for Play Scene.
|
||||
func (u *PlayScene) setupMenuBar(d *Doodle) *ui.MenuBar {
|
||||
menu := ui.NewMenuBar("Main Menu")
|
||||
|
||||
////////
|
||||
// Game menu
|
||||
gameMenu := menu.AddMenu("Game")
|
||||
gameMenu.AddItem("Story Mode", func() {
|
||||
// TODO: de-duplicate code from MainScene
|
||||
if u.winLevelPacks == nil {
|
||||
u.winLevelPacks = windows.NewLevelPackWindow(windows.LevelPack{
|
||||
Supervisor: u.supervisor,
|
||||
Engine: d.Engine,
|
||||
|
||||
OnPlayLevel: func(lp levelpack.LevelPack, which levelpack.Level) {
|
||||
if err := d.PlayFromLevelpack(lp, which); err != nil {
|
||||
shmem.FlashError(err.Error())
|
||||
}
|
||||
},
|
||||
OnCloseWindow: func() {
|
||||
u.winLevelPacks.Hide()
|
||||
},
|
||||
})
|
||||
}
|
||||
u.winLevelPacks.MoveTo(render.Point{
|
||||
X: (d.width / 2) - (u.winLevelPacks.Size().W / 2),
|
||||
Y: (d.height / 2) - (u.winLevelPacks.Size().H / 2),
|
||||
})
|
||||
u.winLevelPacks.Show()
|
||||
})
|
||||
gameMenu.AddItemAccel("New drawing", "Ctrl-N", d.GotoNewMenu)
|
||||
gameMenu.AddItemAccel("Open drawing", "Ctrl-O", d.GotoLoadMenu)
|
||||
|
||||
gameMenu.AddSeparator()
|
||||
gameMenu.AddItem("Quit to menu", func() {
|
||||
d.Goto(&MainScene{})
|
||||
})
|
||||
gameMenu.AddItemAccel("Quit", "Escape", func() {
|
||||
d.ConfirmExit()
|
||||
})
|
||||
|
||||
////////
|
||||
// Level menu
|
||||
levelMenu := menu.AddMenu("Level")
|
||||
levelMenu.AddItemAccel("Edit level", "E", u.EditLevel)
|
||||
|
||||
// Hilariously broken, someday!
|
||||
if usercfg.Current.EnableFeatures {
|
||||
levelMenu.AddSeparator()
|
||||
levelMenu.AddItemAccel("New viewport", "v", func() {
|
||||
pip := windows.MakePiPWindow(d.width, d.height, windows.PiP{
|
||||
Supervisor: u.supervisor,
|
||||
Engine: u.d.Engine,
|
||||
Level: u.Level,
|
||||
Event: u.d.event,
|
||||
})
|
||||
|
||||
pip.Show()
|
||||
})
|
||||
}
|
||||
|
||||
d.MakeHelpMenu(menu, u.supervisor)
|
||||
|
||||
menu.Supervise(u.supervisor)
|
||||
menu.Compute(d.Engine)
|
||||
|
||||
return menu
|
||||
}
|
|
@ -28,6 +28,13 @@ func (s *PlayScene) LoopTouchable(ev *event.State) {
|
|||
cursor = render.NewPoint(ev.CursorX, ev.CursorY)
|
||||
)
|
||||
|
||||
// Don't do any of this if the mouse is over the menu bar, so
|
||||
// clicking on the menus doesn't make the character move or jump.
|
||||
if cursor.Inside(s.menubar.Rect()) || s.supervisor.GetModal() != nil ||
|
||||
s.supervisor.IsPointInWindow(cursor) {
|
||||
return
|
||||
}
|
||||
|
||||
// Detect if the player is idle.
|
||||
// Idle means that they are not holding any directional or otherwise input key.
|
||||
// Keyboard inputs and touch events from this function will set these keys.
|
||||
|
|
|
@ -42,7 +42,8 @@ func NewJSProxy(vm *VM) JSProxy {
|
|||
return shmem.Tick
|
||||
},
|
||||
"time": map[string]interface{}{
|
||||
"Now": time.Now,
|
||||
"Now": time.Now,
|
||||
"Since": time.Since,
|
||||
"Add": func(t time.Time, ms int64) time.Time {
|
||||
return t.Add(time.Duration(ms) * time.Millisecond)
|
||||
},
|
||||
|
|
|
@ -36,6 +36,7 @@ type Settings struct {
|
|||
CrosshairSize int `json:",omitempty"`
|
||||
CrosshairColor render.Color
|
||||
HideTouchHints bool `json:",omitempty"`
|
||||
DisableAutosave bool `json:",omitempty"`
|
||||
|
||||
// Secret boolprops from balance/boolprops.go
|
||||
ShowHiddenDoodads bool `json:",omitempty"`
|
||||
|
|
|
@ -39,7 +39,7 @@ func NewLevelPackWindow(config LevelPack) *ui.Window {
|
|||
|
||||
// size of the popup window
|
||||
width = 320
|
||||
height = 340
|
||||
height = 360
|
||||
)
|
||||
|
||||
// Get the available .levelpack files.
|
||||
|
@ -275,7 +275,7 @@ func (config LevelPack) makeDetailScreen(frame *ui.Frame, width, height int, lp
|
|||
buttonWidth = width - 40
|
||||
|
||||
page = 1
|
||||
perPage = 3
|
||||
perPage = 4
|
||||
pages = int(
|
||||
math.Ceil(
|
||||
float64(len(lp.Levels)) / float64(perPage),
|
||||
|
|
|
@ -77,25 +77,37 @@ func NewPiPWindow(cfg PiP) *ui.Window {
|
|||
canvas.Editable = true
|
||||
canvas.Resize(render.NewRect(canvasWidth, canvasHeight))
|
||||
|
||||
// If we have tool bindings to edit in PiP window
|
||||
var (
|
||||
editable bool
|
||||
curTool drawtool.Tool
|
||||
curThicc int
|
||||
)
|
||||
if cfg.Tool != nil && cfg.BrushSize != nil {
|
||||
editable = true
|
||||
curTool = *cfg.Tool
|
||||
curThicc = *cfg.BrushSize
|
||||
canvas.Tool = curTool
|
||||
}
|
||||
|
||||
// NOTE: my UI toolkit calls this every tick, if this is "fixed"
|
||||
// in the future make one that does.
|
||||
var (
|
||||
curTool = *cfg.Tool
|
||||
curThicc = *cfg.BrushSize
|
||||
)
|
||||
canvas.Tool = curTool
|
||||
window.Handle(ui.MouseMove, func(ed ui.EventData) error {
|
||||
canvas.Loop(cfg.Event)
|
||||
|
||||
// Check if bound values have modified.
|
||||
if *cfg.Tool != curTool {
|
||||
curTool = *cfg.Tool
|
||||
canvas.Tool = curTool
|
||||
}
|
||||
if *cfg.BrushSize != curThicc {
|
||||
curThicc = *cfg.BrushSize
|
||||
canvas.BrushSize = curThicc
|
||||
// Did we have tool bindings for an editable PiP?
|
||||
if editable {
|
||||
// Check if bound values have modified.
|
||||
if *cfg.Tool != curTool {
|
||||
curTool = *cfg.Tool
|
||||
canvas.Tool = curTool
|
||||
}
|
||||
if *cfg.BrushSize != curThicc {
|
||||
curThicc = *cfg.BrushSize
|
||||
canvas.BrushSize = curThicc
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ type Settings struct {
|
|||
CrosshairSize *int
|
||||
CrosshairColor *render.Color
|
||||
HideTouchHints *bool
|
||||
DisableAutosave *bool
|
||||
|
||||
// Configuration options.
|
||||
SceneName string // name of scene which called this window
|
||||
|
@ -142,6 +143,12 @@ func (c Settings) makeOptionsTab(tabFrame *ui.TabFrame, Width, Height int) *ui.F
|
|||
PadX: 4,
|
||||
name: "toolbars",
|
||||
},
|
||||
{
|
||||
Boolean: c.DisableAutosave,
|
||||
Text: "Disable auto-save in the Editor",
|
||||
PadX: 4,
|
||||
name: "autosave",
|
||||
},
|
||||
{
|
||||
Integer: c.CrosshairSize,
|
||||
Text: "Editor: Crosshair size (0 to disable):",
|
||||
|
@ -170,9 +177,7 @@ func (c Settings) makeOptionsTab(tabFrame *ui.TabFrame, Width, Height int) *ui.F
|
|||
},
|
||||
{
|
||||
Text: "Levels and doodads you create in-game are placed in your\n" +
|
||||
"Profile Directory. This is also where you can place content made\n" +
|
||||
"by others to use them in your game. Click on the button below\n" +
|
||||
"to (hopefully) be taken to your Profile Directory:",
|
||||
"Profile Directory, which you can access below:",
|
||||
},
|
||||
}
|
||||
for _, row := range rows {
|
||||
|
@ -634,20 +639,11 @@ func (c Settings) makeExperimentalTab(tabFrame *ui.TabFrame, Width, Height int)
|
|||
PadY: 2,
|
||||
},
|
||||
{
|
||||
Header: "Zoom In/Out",
|
||||
Header: "Viewport window",
|
||||
},
|
||||
{
|
||||
Text: "This adds Zoom options to the level editor. It has a few\n" +
|
||||
"bugs around scrolling but may be useful already.",
|
||||
PadY: 2,
|
||||
},
|
||||
{
|
||||
Header: "Replace Level Palette",
|
||||
},
|
||||
{
|
||||
Text: "This adds an option to the Level Properties dialog to\n" +
|
||||
"replace your level palette with one of the defaults,\n" +
|
||||
"like on the New Level screen. It might not actually work.",
|
||||
Text: "This option in the Level menu opens another view into\n" +
|
||||
"the level. Has glitchy wallpaper problems.",
|
||||
PadY: 2,
|
||||
},
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue
Block a user