Async Giant Screenshot, Player Physics and UI Polish
* The "Giant Screenshot" feature takes a very long time, so it is made asynchronous. If you try and run a second one while the first is busy, you get an error flash. You can continue editing the level, even playtest it, or load a different level, and it will continue crunching on the Giant Screenshot and flash when it's finished. * Updated the player physics to use proper Velocity to jump off the ground rather than the hacky timer-based fixed speed approach. * FlashError() function to flash "error level" messages to the screen. They appear in orange text instead of the usual blue, and most error messages in the game use this now. The dev console "error <msg>" command can simulate an error message. * Flashed message fonts are updated. The blue font now uses softer stroke and shadow colors and the same algorithm applies to the orange error flashes. Some other changes to player physics: * Max velocity, acceleration speed, and gravity have been tweaked. * Fast turn-around if you are moving right and then need to go left. Your velocity resets to zero at the transition so you quickly get going the way you want to go. Some levels that need a bit of love for the new platforming physics: * Tutorial 3.level
This commit is contained in:
parent
0b0af70a62
commit
fb5a8a1ae8
|
@ -23,10 +23,11 @@ var (
|
||||||
}
|
}
|
||||||
|
|
||||||
// Player speeds
|
// Player speeds
|
||||||
PlayerMaxVelocity float64 = 6
|
PlayerMaxVelocity float64 = 7
|
||||||
PlayerAcceleration float64 = 0.9
|
PlayerJumpVelocity float64 = -20
|
||||||
|
PlayerAcceleration float64 = 0.12
|
||||||
Gravity float64 = 6
|
Gravity float64 = 6
|
||||||
GravityAcceleration float64 = 0.2
|
GravityAcceleration float64 = 0.1
|
||||||
SlopeMaxHeight = 8 // max pixel height for player to walk up a slope
|
SlopeMaxHeight = 8 // max pixel height for player to walk up a slope
|
||||||
|
|
||||||
// Default chunk size for canvases.
|
// Default chunk size for canvases.
|
||||||
|
|
|
@ -71,6 +71,20 @@ var (
|
||||||
WindowBackground = render.MustHexColor("#cdb689")
|
WindowBackground = render.MustHexColor("#cdb689")
|
||||||
WindowBorder = render.Grey
|
WindowBorder = render.Grey
|
||||||
|
|
||||||
|
// Developer Shell and Flashed Messages styles.
|
||||||
|
FlashStrokeDarken = 60
|
||||||
|
FlashShadowDarken = 120
|
||||||
|
FlashFont = func(text string) render.Text {
|
||||||
|
return render.Text{
|
||||||
|
Text: text,
|
||||||
|
Size: 18,
|
||||||
|
Color: render.SkyBlue,
|
||||||
|
Stroke: render.SkyBlue.Darken(FlashStrokeDarken),
|
||||||
|
Shadow: render.SkyBlue.Darken(FlashShadowDarken),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FlashErrorColor = render.MustHexColor("#FF9900")
|
||||||
|
|
||||||
// Menu bar styles.
|
// Menu bar styles.
|
||||||
MenuBackground = render.Black
|
MenuBackground = render.Black
|
||||||
MenuFont = render.Text{
|
MenuFont = render.Text{
|
||||||
|
|
|
@ -28,7 +28,7 @@ func (c Command) cheatCommand(d *Doodle) bool {
|
||||||
playScene.drawing.Editable = true
|
playScene.drawing.Editable = true
|
||||||
d.Flash("Level canvas is now editable. Don't edit and drive!")
|
d.Flash("Level canvas is now editable. Don't edit and drive!")
|
||||||
} else {
|
} else {
|
||||||
d.Flash("Use this cheat in Play Mode to make the level canvas editable.")
|
d.FlashError("Use this cheat in Play Mode to make the level canvas editable.")
|
||||||
}
|
}
|
||||||
|
|
||||||
case "scroll scroll scroll your boat":
|
case "scroll scroll scroll your boat":
|
||||||
|
@ -36,7 +36,7 @@ func (c Command) cheatCommand(d *Doodle) bool {
|
||||||
playScene.drawing.Scrollable = true
|
playScene.drawing.Scrollable = true
|
||||||
d.Flash("Level canvas is now scrollable with the arrow keys.")
|
d.Flash("Level canvas is now scrollable with the arrow keys.")
|
||||||
} else {
|
} else {
|
||||||
d.Flash("Use this cheat in Play Mode to make the level scrollable.")
|
d.FlashError("Use this cheat in Play Mode to make the level scrollable.")
|
||||||
}
|
}
|
||||||
|
|
||||||
case "import antigravity":
|
case "import antigravity":
|
||||||
|
@ -50,7 +50,7 @@ func (c Command) cheatCommand(d *Doodle) bool {
|
||||||
d.Flash("Gravity restored for player character.")
|
d.Flash("Gravity restored for player character.")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
d.Flash("Use this cheat in Play Mode to disable gravity for the player character.")
|
d.FlashError("Use this cheat in Play Mode to disable gravity for the player character.")
|
||||||
}
|
}
|
||||||
|
|
||||||
case "ghost mode":
|
case "ghost mode":
|
||||||
|
@ -67,7 +67,7 @@ func (c Command) cheatCommand(d *Doodle) bool {
|
||||||
d.Flash("Clipping and gravity restored for player character.")
|
d.Flash("Clipping and gravity restored for player character.")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
d.Flash("Use this cheat in Play Mode to disable clipping for the player character.")
|
d.FlashError("Use this cheat in Play Mode to disable clipping for the player character.")
|
||||||
}
|
}
|
||||||
|
|
||||||
case "show all actors":
|
case "show all actors":
|
||||||
|
@ -77,7 +77,7 @@ func (c Command) cheatCommand(d *Doodle) bool {
|
||||||
}
|
}
|
||||||
d.Flash("All invisible actors made visible.")
|
d.Flash("All invisible actors made visible.")
|
||||||
} else {
|
} else {
|
||||||
d.Flash("Use this cheat in Play Mode to show hidden actors, such as technical doodads.")
|
d.FlashError("Use this cheat in Play Mode to show hidden actors, such as technical doodads.")
|
||||||
}
|
}
|
||||||
|
|
||||||
case "give all keys":
|
case "give all keys":
|
||||||
|
@ -89,7 +89,7 @@ func (c Command) cheatCommand(d *Doodle) bool {
|
||||||
playScene.Player.AddItem("small-key.doodad", 99)
|
playScene.Player.AddItem("small-key.doodad", 99)
|
||||||
d.Flash("Given all keys to the player character.")
|
d.Flash("Given all keys to the player character.")
|
||||||
} else {
|
} else {
|
||||||
d.Flash("Use this cheat in Play Mode to get all colored keys.")
|
d.FlashError("Use this cheat in Play Mode to get all colored keys.")
|
||||||
}
|
}
|
||||||
|
|
||||||
case "drop all items":
|
case "drop all items":
|
||||||
|
@ -97,7 +97,7 @@ func (c Command) cheatCommand(d *Doodle) bool {
|
||||||
playScene.Player.ClearInventory()
|
playScene.Player.ClearInventory()
|
||||||
d.Flash("Cleared inventory of player character.")
|
d.Flash("Cleared inventory of player character.")
|
||||||
} else {
|
} else {
|
||||||
d.Flash("Use this cheat in Play Mode to clear your inventory.")
|
d.FlashError("Use this cheat in Play Mode to clear your inventory.")
|
||||||
}
|
}
|
||||||
|
|
||||||
case "fly like a bird":
|
case "fly like a bird":
|
||||||
|
|
|
@ -39,6 +39,9 @@ func (c Command) Run(d *Doodle) error {
|
||||||
case "echo":
|
case "echo":
|
||||||
d.Flash(c.ArgsLiteral)
|
d.Flash(c.ArgsLiteral)
|
||||||
return nil
|
return nil
|
||||||
|
case "error":
|
||||||
|
d.FlashError(c.ArgsLiteral)
|
||||||
|
return nil
|
||||||
case "alert":
|
case "alert":
|
||||||
modal.Alert(c.ArgsLiteral)
|
modal.Alert(c.ArgsLiteral)
|
||||||
return nil
|
return nil
|
||||||
|
@ -104,13 +107,13 @@ func (c Command) Close(d *Doodle) error {
|
||||||
// ExtractBindata dumps the app's embedded bindata to the filesystem.
|
// ExtractBindata dumps the app's embedded bindata to the filesystem.
|
||||||
func (c Command) ExtractBindata(d *Doodle, path string) error {
|
func (c Command) ExtractBindata(d *Doodle, path string) error {
|
||||||
if len(path) == 0 || path[0] != '/' {
|
if len(path) == 0 || path[0] != '/' {
|
||||||
d.Flash("Required: an absolute path to a directory to extract to.")
|
d.FlashError("Required: an absolute path to a directory to extract to.")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err := os.MkdirAll(path, 0755)
|
err := os.MkdirAll(path, 0755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
d.Flash("MkdirAll: %s", err)
|
d.FlashError("MkdirAll: %s", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,7 +123,7 @@ func (c Command) ExtractBindata(d *Doodle, path string) error {
|
||||||
|
|
||||||
data, err := assets.Asset(filename)
|
data, err := assets.Asset(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
d.Flash("error on file %s: %s", filename, err)
|
d.FlashError("error on file %s: %s", filename, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,7 +134,7 @@ func (c Command) ExtractBindata(d *Doodle, path string) error {
|
||||||
|
|
||||||
fh, err := os.Create(outfile)
|
fh, err := os.Create(outfile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
d.Flash("error writing file %s: %s", outfile, err)
|
d.FlashError("error writing file %s: %s", outfile, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
fh.Write(data)
|
fh.Write(data)
|
||||||
|
@ -145,7 +148,7 @@ func (c Command) ExtractBindata(d *Doodle, path string) error {
|
||||||
// Help prints the help info.
|
// Help prints the help info.
|
||||||
func (c Command) Help(d *Doodle) error {
|
func (c Command) Help(d *Doodle) error {
|
||||||
if len(c.Args) == 0 {
|
if len(c.Args) == 0 {
|
||||||
d.Flash("Available commands: new save edit play quit echo")
|
d.Flash("Available commands: new save edit play quit echo error")
|
||||||
d.Flash(" alert clear help boolProp eval repl")
|
d.Flash(" alert clear help boolProp eval repl")
|
||||||
d.Flash("Type `help` and then the command, like: `help edit`")
|
d.Flash("Type `help` and then the command, like: `help edit`")
|
||||||
return nil
|
return nil
|
||||||
|
@ -155,6 +158,9 @@ func (c Command) Help(d *Doodle) error {
|
||||||
case "echo":
|
case "echo":
|
||||||
d.Flash("Usage: echo <message>")
|
d.Flash("Usage: echo <message>")
|
||||||
d.Flash("Flash a message back to the console")
|
d.Flash("Flash a message back to the console")
|
||||||
|
case "error":
|
||||||
|
d.Flash("Usage: error <message>")
|
||||||
|
d.Flash("Flash an error message back to the console")
|
||||||
case "alert":
|
case "alert":
|
||||||
d.Flash("Usage: alert <message>")
|
d.Flash("Usage: alert <message>")
|
||||||
d.Flash("Pop up an Alert box with a custom message")
|
d.Flash("Pop up an Alert box with a custom message")
|
||||||
|
@ -294,7 +300,7 @@ func (c Command) BoolProp(d *Doodle) error {
|
||||||
} else {
|
} else {
|
||||||
// Try the global boolProps in balance package.
|
// Try the global boolProps in balance package.
|
||||||
if err := balance.BoolProp(name, truthy); err != nil {
|
if err := balance.BoolProp(name, truthy); err != nil {
|
||||||
d.Flash("%s", err)
|
d.FlashError("%s", err)
|
||||||
} else {
|
} else {
|
||||||
d.Flash("%s: %+v", name, truthy)
|
d.Flash("%s: %+v", name, truthy)
|
||||||
}
|
}
|
||||||
|
@ -307,7 +313,7 @@ func (c Command) BoolProp(d *Doodle) error {
|
||||||
func (c Command) RunScript(d *Doodle, code interface{}) (otto.Value, error) {
|
func (c Command) RunScript(d *Doodle, code interface{}) (otto.Value, error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
d.Flash("Panic: %s", err)
|
d.FlashError("Command.RunScript: Panic: %s", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
out, err := d.shell.js.Run(code)
|
out, err := d.shell.js.Run(code)
|
||||||
|
|
|
@ -269,7 +269,7 @@ func (d *Doodle) NewDoodad(size int) {
|
||||||
if answer != "" {
|
if answer != "" {
|
||||||
i, err := strconv.Atoi(answer)
|
i, err := strconv.Atoi(answer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
d.Flash("Error: Doodad size must be a number.")
|
d.FlashError("Error: Doodad size must be a number.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
size = i
|
size = i
|
||||||
|
@ -277,7 +277,7 @@ func (d *Doodle) NewDoodad(size int) {
|
||||||
|
|
||||||
// Recurse with the proper answer.
|
// Recurse with the proper answer.
|
||||||
if size <= 0 {
|
if size <= 0 {
|
||||||
d.Flash("Error: Doodad size must be a positive number.")
|
d.FlashError("Error: Doodad size must be a positive number.")
|
||||||
}
|
}
|
||||||
d.NewDoodad(size)
|
d.NewDoodad(size)
|
||||||
})
|
})
|
||||||
|
|
|
@ -127,7 +127,7 @@ func (s *EditorScene) setupAsync(d *Doodle) error {
|
||||||
"Opening: " + s.filename,
|
"Opening: " + s.filename,
|
||||||
)
|
)
|
||||||
if err := s.LoadLevel(s.filename); err != nil {
|
if err := s.LoadLevel(s.filename); err != nil {
|
||||||
d.Flash("LoadLevel error: %s", err)
|
d.FlashError("LoadLevel error: %s", err)
|
||||||
} else {
|
} else {
|
||||||
s.UI.Canvas.InstallActors(s.Level.Actors)
|
s.UI.Canvas.InstallActors(s.Level.Actors)
|
||||||
}
|
}
|
||||||
|
@ -138,7 +138,7 @@ func (s *EditorScene) setupAsync(d *Doodle) error {
|
||||||
if usercfg.Current.WriteLockOverride {
|
if usercfg.Current.WriteLockOverride {
|
||||||
d.Flash("Note: write lock has been overridden")
|
d.Flash("Note: write lock has been overridden")
|
||||||
} else {
|
} else {
|
||||||
d.Flash("That level is write-protected and cannot be viewed in the editor.")
|
d.FlashError("That level is write-protected and cannot be viewed in the editor.")
|
||||||
s.Level = nil
|
s.Level = nil
|
||||||
s.UI.Canvas.ClearActors()
|
s.UI.Canvas.ClearActors()
|
||||||
s.filename = ""
|
s.filename = ""
|
||||||
|
@ -165,7 +165,7 @@ func (s *EditorScene) setupAsync(d *Doodle) error {
|
||||||
if s.filename != "" && s.OpenFile {
|
if s.filename != "" && s.OpenFile {
|
||||||
log.Debug("EditorScene.Setup: Loading doodad from filename at %s", s.filename)
|
log.Debug("EditorScene.Setup: Loading doodad from filename at %s", s.filename)
|
||||||
if err := s.LoadDoodad(s.filename); err != nil {
|
if err := s.LoadDoodad(s.filename); err != nil {
|
||||||
d.Flash("LoadDoodad error: %s", err)
|
d.FlashError("LoadDoodad error: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,7 +174,7 @@ func (s *EditorScene) setupAsync(d *Doodle) error {
|
||||||
if usercfg.Current.WriteLockOverride {
|
if usercfg.Current.WriteLockOverride {
|
||||||
d.Flash("Note: write lock has been overridden")
|
d.Flash("Note: write lock has been overridden")
|
||||||
} else {
|
} else {
|
||||||
d.Flash("That doodad is write-protected and cannot be viewed in the editor.")
|
d.FlashError("That doodad is write-protected and cannot be viewed in the editor.")
|
||||||
s.Doodad = nil
|
s.Doodad = nil
|
||||||
s.filename = ""
|
s.filename = ""
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ func (u *EditorUI) SetupMenuBar(d *Doodle) *ui.MenuBar {
|
||||||
drawingType = "level"
|
drawingType = "level"
|
||||||
saveFunc = func(filename string) {
|
saveFunc = func(filename string) {
|
||||||
if err := u.Scene.SaveLevel(filename); err != nil {
|
if err := u.Scene.SaveLevel(filename); err != nil {
|
||||||
d.Flash("Error: %s", err)
|
d.FlashError("Error: %s", err)
|
||||||
} else {
|
} else {
|
||||||
d.Flash("Saved level: %s", filename)
|
d.Flash("Saved level: %s", filename)
|
||||||
}
|
}
|
||||||
|
@ -42,13 +42,13 @@ func (u *EditorUI) SetupMenuBar(d *Doodle) *ui.MenuBar {
|
||||||
drawingType = "doodad"
|
drawingType = "doodad"
|
||||||
saveFunc = func(filename string) {
|
saveFunc = func(filename string) {
|
||||||
if err := u.Scene.SaveDoodad(filename); err != nil {
|
if err := u.Scene.SaveDoodad(filename); err != nil {
|
||||||
d.Flash("Error: %s", err)
|
d.FlashError("Error: %s", err)
|
||||||
} else {
|
} else {
|
||||||
d.Flash("Saved doodad: %s", filename)
|
d.Flash("Saved doodad: %s", filename)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
d.Flash("Error: Scene.DrawingType is not a valid type")
|
d.FlashError("Error: Scene.DrawingType is not a valid type")
|
||||||
}
|
}
|
||||||
|
|
||||||
////////
|
////////
|
||||||
|
@ -127,13 +127,17 @@ func (u *EditorUI) SetupMenuBar(d *Doodle) *ui.MenuBar {
|
||||||
|
|
||||||
levelMenu.AddSeparator()
|
levelMenu.AddSeparator()
|
||||||
levelMenu.AddItem("Giant Screenshot", func() {
|
levelMenu.AddItem("Giant Screenshot", func() {
|
||||||
|
// It takes a LONG TIME to render for medium+ maps.
|
||||||
|
// Do so on a background thread.
|
||||||
|
go func() {
|
||||||
filename, err := giant_screenshot.SaveGiantScreenshot(u.Scene.Level)
|
filename, err := giant_screenshot.SaveGiantScreenshot(u.Scene.Level)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
d.Flash(err.Error())
|
d.FlashError("Error: %s", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
d.Flash("Saved screenshot to: %s", filename)
|
d.FlashError("Giant screenshot saved as: %s", filename)
|
||||||
|
}()
|
||||||
})
|
})
|
||||||
levelMenu.AddItem("Open screenshot folder", func() {
|
levelMenu.AddItem("Open screenshot folder", func() {
|
||||||
native.OpenLocalURL(userdir.ScreenshotDirectory)
|
native.OpenLocalURL(userdir.ScreenshotDirectory)
|
||||||
|
@ -326,7 +330,7 @@ func (s *EditorScene) MenuSave(as bool) func() {
|
||||||
// drawingType = "level"
|
// drawingType = "level"
|
||||||
saveFunc = func(filename string) {
|
saveFunc = func(filename string) {
|
||||||
if err := s.SaveLevel(filename); err != nil {
|
if err := s.SaveLevel(filename); err != nil {
|
||||||
s.d.Flash("Error: %s", err)
|
s.d.FlashError("Error: %s", err)
|
||||||
} else {
|
} else {
|
||||||
s.d.Flash("Saved level: %s", filename)
|
s.d.Flash("Saved level: %s", filename)
|
||||||
}
|
}
|
||||||
|
@ -335,13 +339,13 @@ func (s *EditorScene) MenuSave(as bool) func() {
|
||||||
// drawingType = "doodad"
|
// drawingType = "doodad"
|
||||||
saveFunc = func(filename string) {
|
saveFunc = func(filename string) {
|
||||||
if err := s.SaveDoodad(filename); err != nil {
|
if err := s.SaveDoodad(filename); err != nil {
|
||||||
s.d.Flash("Error: %s", err)
|
s.d.FlashError("Error: %s", err)
|
||||||
} else {
|
} else {
|
||||||
s.d.Flash("Saved doodad: %s", filename)
|
s.d.Flash("Saved doodad: %s", filename)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
s.d.Flash("Error: Scene.DrawingType is not a valid type")
|
s.d.FlashError("Error: Scene.DrawingType is not a valid type")
|
||||||
}
|
}
|
||||||
|
|
||||||
// "Save As"?
|
// "Save As"?
|
||||||
|
|
|
@ -182,7 +182,7 @@ func (u *EditorUI) SetupPopups(d *Doodle) {
|
||||||
u.licenseWindow.Show()
|
u.licenseWindow.Show()
|
||||||
u.Supervisor.FocusWindow(u.licenseWindow)
|
u.Supervisor.FocusWindow(u.licenseWindow)
|
||||||
}
|
}
|
||||||
d.Flash("Level Publishing is only available in the full version of the game.")
|
d.FlashError("Level Publishing is only available in the full version of the game.")
|
||||||
// modal.Alert(
|
// modal.Alert(
|
||||||
// "This feature is only available in the full version of the game.",
|
// "This feature is only available in the full version of the game.",
|
||||||
// ).WithTitle("Please register")
|
// ).WithTitle("Please register")
|
||||||
|
@ -193,7 +193,7 @@ func (u *EditorUI) SetupPopups(d *Doodle) {
|
||||||
cwd, _ := os.Getwd()
|
cwd, _ := os.Getwd()
|
||||||
d.Prompt(fmt.Sprintf("File name (relative to %s)> ", cwd), func(answer string) {
|
d.Prompt(fmt.Sprintf("File name (relative to %s)> ", cwd), func(answer string) {
|
||||||
if answer == "" {
|
if answer == "" {
|
||||||
d.Flash("A file name is required to publish this level.")
|
d.FlashError("A file name is required to publish this level.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -364,7 +364,7 @@ func (u *EditorUI) SetupPopups(d *Doodle) {
|
||||||
},
|
},
|
||||||
OnChangeLayer: func(index int) {
|
OnChangeLayer: func(index int) {
|
||||||
if index < 0 || index >= len(scene.Doodad.Layers) {
|
if index < 0 || index >= len(scene.Doodad.Layers) {
|
||||||
d.Flash("OnChangeLayer: layer %d out of range", index)
|
d.FlashError("OnChangeLayer: layer %d out of range", index)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package giant_screenshot
|
package giant_screenshot
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"image"
|
"image"
|
||||||
"image/draw"
|
"image/draw"
|
||||||
"image/png"
|
"image/png"
|
||||||
|
@ -11,6 +12,7 @@ import (
|
||||||
"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"
|
||||||
|
"git.kirsle.net/apps/doodle/pkg/shmem"
|
||||||
"git.kirsle.net/apps/doodle/pkg/userdir"
|
"git.kirsle.net/apps/doodle/pkg/userdir"
|
||||||
"git.kirsle.net/apps/doodle/pkg/wallpaper"
|
"git.kirsle.net/apps/doodle/pkg/wallpaper"
|
||||||
"git.kirsle.net/go/render"
|
"git.kirsle.net/go/render"
|
||||||
|
@ -20,8 +22,25 @@ import (
|
||||||
Giant Screenshot functionality for the Level Editor.
|
Giant Screenshot functionality for the Level Editor.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
var locked bool
|
||||||
|
|
||||||
// GiantScreenshot returns a rendered RGBA image of the entire level.
|
// GiantScreenshot returns a rendered RGBA image of the entire level.
|
||||||
func GiantScreenshot(lvl *level.Level) image.Image {
|
//
|
||||||
|
// Only one thread should be doing this at a time. A sync.Mutex will cause
|
||||||
|
// an error to return if another goroutine is already in the process of
|
||||||
|
// generating a screenshot, and you'll have to wait and try later.
|
||||||
|
func GiantScreenshot(lvl *level.Level) (image.Image, error) {
|
||||||
|
// Lock this to one user at a time.
|
||||||
|
if locked {
|
||||||
|
return nil, errors.New("a giant screenshot is still being processed; try later...")
|
||||||
|
}
|
||||||
|
locked = true
|
||||||
|
defer func() {
|
||||||
|
locked = false
|
||||||
|
}()
|
||||||
|
|
||||||
|
shmem.Flash("Saving a giant screenshot (this takes a moment)...")
|
||||||
|
|
||||||
// How big will our image be?
|
// How big will our image be?
|
||||||
var (
|
var (
|
||||||
size = lvl.Chunker.WorldSizePositive()
|
size = lvl.Chunker.WorldSizePositive()
|
||||||
|
@ -110,7 +129,7 @@ func GiantScreenshot(lvl *level.Level) image.Image {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return img
|
return img, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveGiantScreenshot will take a screenshot and write it to a file on disk,
|
// SaveGiantScreenshot will take a screenshot and write it to a file on disk,
|
||||||
|
@ -118,7 +137,10 @@ func GiantScreenshot(lvl *level.Level) image.Image {
|
||||||
func SaveGiantScreenshot(level *level.Level) (string, error) {
|
func SaveGiantScreenshot(level *level.Level) (string, error) {
|
||||||
var filename = time.Now().Format("2006-01-02_15-04-05.png")
|
var filename = time.Now().Format("2006-01-02_15-04-05.png")
|
||||||
|
|
||||||
img := GiantScreenshot(level)
|
img, err := GiantScreenshot(level)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
fh, err := os.Create(filepath.Join(userdir.ScreenshotDirectory, filename))
|
fh, err := os.Create(filepath.Join(userdir.ScreenshotDirectory, filename))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -117,7 +117,7 @@ func (s *MenuScene) Setup(d *Doodle) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
d.Flash("No Valid StartupMenu Given to MenuScene")
|
d.FlashError("No Valid StartupMenu Given to MenuScene")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Whatever window we got, give it window manager controls under Supervisor.
|
// Whatever window we got, give it window manager controls under Supervisor.
|
||||||
|
|
|
@ -65,7 +65,7 @@ func (s *PlayScene) computeInventory() {
|
||||||
// Cache miss. Load the doodad here.
|
// Cache miss. Load the doodad here.
|
||||||
doodad, err := doodads.LoadFile(filename)
|
doodad, err := doodads.LoadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.d.Flash("Inventory item '%s' error: %s", filename, err)
|
s.d.FlashError("Inventory item '%s' error: %s", filename, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,7 @@ type PlayScene struct {
|
||||||
Player *uix.Actor
|
Player *uix.Actor
|
||||||
playerPhysics *physics.Mover
|
playerPhysics *physics.Mover
|
||||||
lastCheckpoint render.Point
|
lastCheckpoint render.Point
|
||||||
|
playerLastDirection float64 // player's heading last tick
|
||||||
antigravity bool // Cheat: disable player gravity
|
antigravity bool // Cheat: disable player gravity
|
||||||
noclip bool // Cheat: disable player clipping
|
noclip bool // Cheat: disable player clipping
|
||||||
playerJumpCounter int // limit jump length
|
playerJumpCounter int // limit jump length
|
||||||
|
@ -200,7 +201,7 @@ func (s *PlayScene) setupAsync(d *Doodle) error {
|
||||||
if s.CanEdit {
|
if s.CanEdit {
|
||||||
d.Flash("Entered Play Mode. Press 'E' to edit this map.")
|
d.Flash("Entered Play Mode. Press 'E' to edit this map.")
|
||||||
} else {
|
} else {
|
||||||
d.Flash("%s", s.Level.Title)
|
d.FlashError("%s", s.Level.Title)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pre-cache all bitmap images from the level chunks.
|
// Pre-cache all bitmap images from the level chunks.
|
||||||
|
@ -272,9 +273,9 @@ func (s *PlayScene) setupPlayer() {
|
||||||
|
|
||||||
// Surface warnings around the spawn flag.
|
// Surface warnings around the spawn flag.
|
||||||
if flagCount == 0 {
|
if flagCount == 0 {
|
||||||
s.d.Flash("Warning: this level contained no Start Flag.")
|
s.d.FlashError("Warning: this level contained no Start Flag.")
|
||||||
} else if flagCount > 1 {
|
} else if flagCount > 1 {
|
||||||
s.d.Flash("Warning: this level contains multiple Start Flags. Player spawn point is ambiguous.")
|
s.d.FlashError("Warning: this level contains multiple Start Flags. Player spawn point is ambiguous.")
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Player = uix.NewActor("PLAYER", &level.Actor{}, player)
|
s.Player = uix.NewActor("PLAYER", &level.Actor{}, player)
|
||||||
|
@ -340,7 +341,7 @@ func (s *PlayScene) BeatLevel() {
|
||||||
|
|
||||||
// FailLevel handles a level failure triggered by a doodad.
|
// FailLevel handles a level failure triggered by a doodad.
|
||||||
func (s *PlayScene) FailLevel(message string) {
|
func (s *PlayScene) FailLevel(message string) {
|
||||||
s.d.Flash(message)
|
s.d.FlashError(message)
|
||||||
s.ShowEndLevelModal(
|
s.ShowEndLevelModal(
|
||||||
false,
|
false,
|
||||||
"You've died!",
|
"You've died!",
|
||||||
|
@ -521,17 +522,29 @@ func (s *PlayScene) movePlayer(ev *event.State) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Up button to signal they want to jump.
|
// Up button to signal they want to jump.
|
||||||
if keybind.Up(ev) && (s.Player.Grounded() || s.playerJumpCounter >= 0) {
|
if keybind.Up(ev) {
|
||||||
jumping = true
|
|
||||||
|
|
||||||
if s.Player.Grounded() {
|
if s.Player.Grounded() {
|
||||||
// Allow them to sustain the jump this many ticks.
|
velocity.Y = balance.PlayerJumpVelocity
|
||||||
s.playerJumpCounter = 32
|
|
||||||
}
|
}
|
||||||
|
} else if velocity.Y < 0 {
|
||||||
|
velocity.Y = 0
|
||||||
}
|
}
|
||||||
|
// if keybind.Up(ev) && (s.Player.Grounded() || s.playerJumpCounter >= 0) {
|
||||||
|
// jumping = true
|
||||||
|
|
||||||
|
// if s.Player.Grounded() {
|
||||||
|
// // Allow them to sustain the jump this many ticks.
|
||||||
|
// s.playerJumpCounter = 32
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
// Moving left or right? Interpolate their velocity by acceleration.
|
// Moving left or right? Interpolate their velocity by acceleration.
|
||||||
if direction != 0 {
|
if direction != 0 {
|
||||||
|
if s.playerLastDirection != direction {
|
||||||
|
log.Error("Changed directions!")
|
||||||
|
velocity.X = 0
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: fast turn-around if they change directions so they don't
|
// TODO: fast turn-around if they change directions so they don't
|
||||||
// slip and slide while their velocity updates.
|
// slip and slide while their velocity updates.
|
||||||
velocity.X = physics.Lerp(
|
velocity.X = physics.Lerp(
|
||||||
|
@ -560,6 +573,8 @@ func (s *PlayScene) movePlayer(ev *event.State) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.playerLastDirection = direction
|
||||||
|
|
||||||
// Move the player unless frozen.
|
// Move the player unless frozen.
|
||||||
// TODO: if Y=0 then gravity fails, but not doing this allows the
|
// TODO: if Y=0 then gravity fails, but not doing this allows the
|
||||||
// player to jump while frozen. Not a HUGE deal right now as only Warp Doors
|
// player to jump while frozen. Not a HUGE deal right now as only Warp Doors
|
||||||
|
|
34
pkg/shell.go
34
pkg/shell.go
|
@ -9,6 +9,7 @@ import (
|
||||||
"git.kirsle.net/apps/doodle/pkg/keybind"
|
"git.kirsle.net/apps/doodle/pkg/keybind"
|
||||||
"git.kirsle.net/apps/doodle/pkg/log"
|
"git.kirsle.net/apps/doodle/pkg/log"
|
||||||
"git.kirsle.net/apps/doodle/pkg/modal/loadscreen"
|
"git.kirsle.net/apps/doodle/pkg/modal/loadscreen"
|
||||||
|
"git.kirsle.net/apps/doodle/pkg/physics"
|
||||||
"git.kirsle.net/apps/doodle/pkg/shmem"
|
"git.kirsle.net/apps/doodle/pkg/shmem"
|
||||||
"git.kirsle.net/go/render"
|
"git.kirsle.net/go/render"
|
||||||
"git.kirsle.net/go/render/event"
|
"git.kirsle.net/go/render/event"
|
||||||
|
@ -22,6 +23,12 @@ func (d *Doodle) Flash(template string, v ...interface{}) {
|
||||||
d.shell.Write(fmt.Sprintf(template, v...))
|
d.shell.Write(fmt.Sprintf(template, v...))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FlashError flashes an error-colored message to the user.
|
||||||
|
func (d *Doodle) FlashError(template string, v ...interface{}) {
|
||||||
|
log.Error(template, v...)
|
||||||
|
d.shell.WriteColorful(fmt.Sprintf(template, v...), balance.FlashErrorColor)
|
||||||
|
}
|
||||||
|
|
||||||
// Prompt the user for a question in the dev console.
|
// Prompt the user for a question in the dev console.
|
||||||
func (d *Doodle) Prompt(question string, callback func(string)) {
|
func (d *Doodle) Prompt(question string, callback func(string)) {
|
||||||
d.shell.Prompt = question
|
d.shell.Prompt = question
|
||||||
|
@ -59,6 +66,7 @@ type Shell struct {
|
||||||
type Flash struct {
|
type Flash struct {
|
||||||
Text string
|
Text string
|
||||||
Expires uint64 // tick that it expires
|
Expires uint64 // tick that it expires
|
||||||
|
Color render.Color
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewShell initializes the shell helper (the "Shellper").
|
// NewShell initializes the shell helper (the "Shellper").
|
||||||
|
@ -80,6 +88,7 @@ func NewShell(d *Doodle) Shell {
|
||||||
"Execute": s.Execute,
|
"Execute": s.Execute,
|
||||||
"RGBA": render.RGBA,
|
"RGBA": render.RGBA,
|
||||||
"Point": render.NewPoint,
|
"Point": render.NewPoint,
|
||||||
|
"Vector": physics.NewVector,
|
||||||
"Rect": render.NewRect,
|
"Rect": render.NewRect,
|
||||||
"Tree": func(w ui.Widget) string {
|
"Tree": func(w ui.Widget) string {
|
||||||
for _, row := range ui.WidgetTree(w) {
|
for _, row := range ui.WidgetTree(w) {
|
||||||
|
@ -160,6 +169,16 @@ func (s *Shell) Write(line string) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WriteError writes a line of error (red) text to the console.
|
||||||
|
func (s *Shell) WriteColorful(line string, color render.Color) {
|
||||||
|
s.Output = append(s.Output, line)
|
||||||
|
s.Flashes = append(s.Flashes, Flash{
|
||||||
|
Text: line,
|
||||||
|
Color: color,
|
||||||
|
Expires: shmem.Tick + balance.FlashTTL,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Parse the command line.
|
// Parse the command line.
|
||||||
func (s *Shell) Parse(input string) Command {
|
func (s *Shell) Parse(input string) Command {
|
||||||
input = strings.TrimSpace(input)
|
input = strings.TrimSpace(input)
|
||||||
|
@ -352,14 +371,15 @@ func (s *Shell) Draw(d *Doodle, ev *event.State) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var text = balance.FlashFont(flash.Text)
|
||||||
|
if !flash.Color.IsZero() {
|
||||||
|
text.Color = flash.Color
|
||||||
|
text.Stroke = text.Color.Darken(balance.FlashStrokeDarken)
|
||||||
|
text.Shadow = text.Color.Darken(balance.FlashShadowDarken)
|
||||||
|
}
|
||||||
|
|
||||||
d.Engine.DrawText(
|
d.Engine.DrawText(
|
||||||
render.Text{
|
text,
|
||||||
Text: flash.Text,
|
|
||||||
Size: balance.ShellFontSize,
|
|
||||||
Color: render.SkyBlue,
|
|
||||||
Stroke: render.Grey,
|
|
||||||
Shadow: render.Black,
|
|
||||||
},
|
|
||||||
render.Point{
|
render.Point{
|
||||||
X: balance.ShellPadding + toolbarWidth,
|
X: balance.ShellPadding + toolbarWidth,
|
||||||
Y: outputY,
|
Y: outputY,
|
||||||
|
|
Loading…
Reference in New Issue
Block a user