Various updates

New doodad interactions:
* Sticky Buttons will emit a "sticky:down" event to linked doodads, with
  a boolean value showing the Sticky Button's state.
* Normal Buttons will listen for "sticky:down" -- when a linked Sticky
  Button is pressed, the normal Button presses in as well, and stays
  pressed while the sticky:down signal is true.
* When the Sticky Button is released (e.g. because it received power
  from another doodad), any linked buttons which were sticky:down
  release as well.
* Switch doodads emit a new "switch:toggle" event JUST BEFORE sending
  the "power" event. Sensitive Doodads can listen for switches in
  particular this way.
* The Electric Door listens for switch:toggle; if a Switch is activated,
  the Electric Door always flips its current state (open to close, or
  vice versa) and ignores the immediately following power event. This
  allows doors to toggle on/off regardless of sync with a Switch.

Other changes:
* When the player character dies by fire, instead of the message saying
  "Watch out for fire!" it will use the name of the fire swatch that
  hurt the player. This way levels could make it say "Watch out for
  spikes!" or "lava" or whatever they want. The "Fire" attribute now
  just means "instantly kills the player."
* Level Editor: You can now edit the Title and Author name of your level
  in the Page Settings window.
* Bugfix: only the player character ends the game by dying in fire.
  Other mobile doodads just turn dark but don't end the game.
* Increase the size of Trapdoor doodad sprites by 150% as they were a
  bit small for the player character.
* Rename the game from "Project: Doodle" to "Sketchy Maze"
This commit is contained in:
Noah 2021-03-30 23:33:25 -07:00
parent 837960c477
commit 76b7dfa4f8
28 changed files with 155 additions and 37 deletions

View File

@ -2,17 +2,30 @@ function main() {
var timer = 0; var timer = 0;
var pressed = false; var pressed = false;
// Has a linked Sticky Button been pressed permanently down?
var stickyDown = false;
Message.Subscribe("sticky:down", function(down) {
stickyDown = down;
Self.ShowLayer(stickyDown ? 1 : 0);
});
Events.OnCollide(function(e) { Events.OnCollide(function(e) {
if (!e.Settled) { if (!e.Settled) {
return; return;
} }
// If a linked Sticky Button is pressed, button stays down too and
// doesn't interact.
if (stickyDown) {
return;
}
// Verify they've touched the button. // Verify they've touched the button.
if (e.Overlap.Y + e.Overlap.H < 24) { if (e.Overlap.Y + e.Overlap.H < 24) {
return; return;
} }
if (!pressed) { if (!pressed && !stickyDown) {
Sound.Play("button-down.wav") Sound.Play("button-down.wav")
Message.Publish("power", true); Message.Publish("power", true);
pressed = true; pressed = true;

View File

@ -8,6 +8,7 @@ function main() {
pressed = false; pressed = false;
Sound.Play("button-up.wav") Sound.Play("button-up.wav")
Message.Publish("power", false); Message.Publish("power", false);
Message.Publish("sticky:down", false);
} }
}) })
@ -29,5 +30,6 @@ function main() {
Self.ShowLayer(1); Self.ShowLayer(1);
pressed = true; pressed = true;
Message.Publish("power", true); Message.Publish("power", true);
Message.Publish("sticky:down", true);
}); });
} }

Binary file not shown.

Before

(image error) Size: 415 B

After

(image error) Size: 5.2 KiB

View File

@ -1,31 +1,59 @@
var animating = false;
var opened = false;
var powerState = false;
// Function to handle the door opening or closing.
function setPoweredState(powered) {
powerState = powered;
console.log("setPoweredState: %+v", powered)
if (powered) {
if (animating || opened) {
return;
}
animating = true;
Sound.Play("electric-door.wav")
Self.PlayAnimation("open", function() {
opened = true;
animating = false;
});
} else {
animating = true;
Sound.Play("electric-door.wav")
Self.PlayAnimation("close", function() {
opened = false;
animating = false;
})
}
}
function main() { function main() {
Self.AddAnimation("open", 100, [0, 1, 2, 3]); Self.AddAnimation("open", 100, [0, 1, 2, 3]);
Self.AddAnimation("close", 100, [3, 2, 1, 0]); Self.AddAnimation("close", 100, [3, 2, 1, 0]);
var animating = false;
var opened = false;
Self.SetHitbox(0, 0, 34, 76); Self.SetHitbox(0, 0, 34, 76);
Message.Subscribe("power", function(powered) { // A linked Switch that activates the door will send the Toggle signal
if (powered) { // immediately before the Power signal. The door can just invert its
if (animating || opened) { // state on this signal, and ignore the very next Power signal. Ordinary
return; // power sources like Buttons will work as normal, as they emit only a power
} // signal.
var ignoreNextPower = false;
Message.Subscribe("switch:toggle", function(powered) {
console.log("A switch powered %+v, setPoweredState(%+v) to opposite", powered, powerState);
ignoreNextPower = true;
setPoweredState(!powerState);
})
animating = true; Message.Subscribe("power", function(powered) {
Sound.Play("electric-door.wav") if (ignoreNextPower) {
Self.PlayAnimation("open", function() { ignoreNextPower = false;
opened = true; return;
animating = false;
});
} else {
animating = true;
Sound.Play("electric-door.wav")
Self.PlayAnimation("close", function() {
opened = false;
animating = false;
})
} }
setPoweredState(powered);
}); });
Events.OnCollide(function(e) { Events.OnCollide(function(e) {

View File

@ -20,6 +20,9 @@ function main() {
if (collide === false) { if (collide === false) {
Sound.Play("button-down.wav") Sound.Play("button-down.wav")
state = !state; state = !state;
var nonce = Math.random() * 2147483647;
Message.Publish("switch:toggle", state);
Message.Publish("power", state); Message.Publish("power", state);
showState(state); showState(state);

Binary file not shown.

Before

(image error) Size: 1020 B

After

(image error) Size: 1.0 KiB

Binary file not shown.

Before

(image error) Size: 970 B

After

(image error) Size: 1.1 KiB

Binary file not shown.

Before

(image error) Size: 1.0 KiB

After

(image error) Size: 1.1 KiB

Binary file not shown.

Before

(image error) Size: 859 B

After

(image error) Size: 1007 B

Binary file not shown.

Before

(image error) Size: 912 B

After

(image error) Size: 1.0 KiB

Binary file not shown.

Before

(image error) Size: 1013 B

After

(image error) Size: 1.1 KiB

Binary file not shown.

Before

(image error) Size: 1009 B

After

(image error) Size: 1.1 KiB

Binary file not shown.

Before

(image error) Size: 765 B

After

(image error) Size: 992 B

Binary file not shown.

Before

(image error) Size: 933 B

After

(image error) Size: 1.0 KiB

Binary file not shown.

Before

(image error) Size: 1011 B

After

(image error) Size: 1.1 KiB

Binary file not shown.

Before

(image error) Size: 1.0 KiB

After

(image error) Size: 1.0 KiB

Binary file not shown.

Before

(image error) Size: 789 B

After

(image error) Size: 991 B

View File

@ -5,8 +5,8 @@ function main() {
var timer = 0; var timer = 0;
// Set our hitbox based on our orientation. // Set our hitbox based on our orientation.
var thickness = 6; var thickness = 10;
var doodadSize = 72; var doodadSize = 86;
if (direction === "left") { if (direction === "left") {
Self.SetHitbox(48, 0, doodadSize, doodadSize); Self.SetHitbox(48, 0, doodadSize, doodadSize);
} else if (direction === "right") { } else if (direction === "right") {

Binary file not shown.

Before

(image error) Size: 831 B

After

(image error) Size: 1.0 KiB

Binary file not shown.

Before

(image error) Size: 964 B

After

(image error) Size: 1.1 KiB

Binary file not shown.

Before

(image error) Size: 1.0 KiB

After

(image error) Size: 1.1 KiB

Binary file not shown.

Before

(image error) Size: 846 B

After

(image error) Size: 1006 B

2
go.mod
View File

@ -24,6 +24,7 @@ require (
github.com/robertkrimen/otto v0.0.0-20200922221731-ef014fd054ac github.com/robertkrimen/otto v0.0.0-20200922221731-ef014fd054ac
github.com/stripe/safesql v0.2.0 // indirect github.com/stripe/safesql v0.2.0 // indirect
github.com/tomnomnom/xtermcolor v0.0.0-20160428124646-b78803f00a7e // indirect github.com/tomnomnom/xtermcolor v0.0.0-20160428124646-b78803f00a7e // indirect
github.com/tsenart/deadcode v0.0.0-20160724212837-210d2dc333e9 // indirect
github.com/urfave/cli v1.22.5 github.com/urfave/cli v1.22.5
github.com/urfave/cli/v2 v2.3.0 github.com/urfave/cli/v2 v2.3.0
github.com/veandco/go-sdl2 v0.4.4 github.com/veandco/go-sdl2 v0.4.4
@ -38,3 +39,4 @@ require (
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b // indirect mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b // indirect
mvdan.cc/unparam v0.0.0-20200501210554-b37ab49443f7 // indirect mvdan.cc/unparam v0.0.0-20200501210554-b37ab49443f7 // indirect
) )

View File

@ -2,11 +2,11 @@ package branding
// Constants for branding and version information. // Constants for branding and version information.
const ( const (
AppName = "Project: Doodle" AppName = "Sketchy Maze"
Summary = "A drawing-based maze game" Summary = "A drawing-based maze game"
Version = "0.4.0-alpha" Version = "0.5.0-alpha"
Website = "https://www.kirsle.net/tagged/Doodle" Website = "https://www.sketchymaze.com"
Copyright = "2020 Noah Petherbridge" Copyright = "2021 Noah Petherbridge"
// Update check URL // Update check URL
UpdateCheckJSON = "https://download.sketchymaze.com/version.json" UpdateCheckJSON = "https://download.sketchymaze.com/version.json"

View File

@ -25,7 +25,7 @@ type Collide struct {
MoveTo render.Point MoveTo render.Point
// Swatch attributes affecting the collision at this time. // Swatch attributes affecting the collision at this time.
InFire bool InFire string // the name of the swatch, Fire = general ouchy color.
InWater bool InWater bool
} }
@ -222,7 +222,7 @@ func CollidesWithGrid(d Actor, grid *level.Chunker, target render.Point) (*Colli
// IsColliding returns whether any sort of collision has occurred. // IsColliding returns whether any sort of collision has occurred.
func (c *Collide) IsColliding() bool { func (c *Collide) IsColliding() bool {
return c.Top || c.Bottom || c.Left || c.Right || return c.Top || c.Bottom || c.Left || c.Right ||
c.InFire || c.InWater c.InFire != "" || c.InWater
} }
// ScanBoundingBox scans all of the pixels in a bounding box on the grid and // ScanBoundingBox scans all of the pixels in a bounding box on the grid and
@ -276,7 +276,7 @@ func (c *Collide) ScanGridLine(p1, p2 render.Point, grid *level.Chunker, side Si
// in our result. If non-solid, we'll collect attributes from it // in our result. If non-solid, we'll collect attributes from it
// and return them in the final result for gameplay behavior. // and return them in the final result for gameplay behavior.
if swatch.Fire { if swatch.Fire {
c.InFire = true c.InFire = swatch.Name
} }
if swatch.Water { if swatch.Water {
c.InWater = true c.InWater = true

View File

@ -138,9 +138,11 @@ func (s *PlayScene) Setup(d *Doodle) error {
// Handler when an actor touches water or fire. // Handler when an actor touches water or fire.
s.drawing.OnLevelCollision = func(a *uix.Actor, col *collision.Collide) { s.drawing.OnLevelCollision = func(a *uix.Actor, col *collision.Collide) {
if col.InFire { if col.InFire != "" {
a.Canvas.MaskColor = render.Black a.Canvas.MaskColor = render.Black
s.DieByFire() if a.ID() == "PLAYER" { // only the player dies in fire.
s.DieByFire(col.InFire)
}
} else if col.InWater { } else if col.InWater {
a.Canvas.MaskColor = render.DarkBlue a.Canvas.MaskColor = render.DarkBlue
} else { } else {
@ -352,11 +354,11 @@ func (s *PlayScene) RestartLevel() {
}) })
} }
// DieByFire ends the level by fire. // DieByFire ends the level by "fire", or w/e the swatch is named.
func (s *PlayScene) DieByFire() { func (s *PlayScene) DieByFire(name string) {
log.Info("Watch out for fire!") log.Info("Watch out for %s!", name)
s.alertBox.Title = "You've died!" s.alertBox.Title = "You've died!"
s.alertBoxLabel.Text = "Watch out for fire!" s.alertBoxLabel.Text = fmt.Sprintf("Watch out for %s!", name)
s.alertReplayButton.Show() s.alertReplayButton.Show()
if s.CanEdit { if s.CanEdit {

View File

@ -108,6 +108,7 @@ func NewCanvas(size int, editable bool) *Canvas {
Editable: editable, Editable: editable,
Scrollable: editable, Scrollable: editable,
Palette: level.NewPalette(), Palette: level.NewPalette(),
BrushSize: 1,
chunks: level.NewChunker(size), chunks: level.NewChunker(size),
actors: make([]*Actor, 0), actors: make([]*Actor, 0),
wallpaper: &Wallpaper{}, wallpaper: &Wallpaper{},

View File

@ -43,7 +43,7 @@ func NewAddEditLevel(config AddEditLevel) *ui.Window {
window.SetButtons(ui.CloseButton) window.SetButtons(ui.CloseButton)
window.Configure(ui.Config{ window.Configure(ui.Config{
Width: 400, Width: 400,
Height: 180, Height: 240,
Background: render.Grey, Background: render.Grey,
}) })
@ -167,6 +167,73 @@ func NewAddEditLevel(config AddEditLevel) *ui.Window {
}(t) }(t)
} }
/******************
* Frame for giving the level a title.
******************/
if config.EditLevel != nil {
label3 := ui.NewLabel(ui.Label{
Text: "Metadata",
Font: balance.LabelFont,
})
frame.Pack(label3, ui.Pack{
Side: ui.N,
FillX: true,
})
type metadataObj struct {
Label string
Binding *string
Update func(string)
}
var metaRows = []metadataObj{
{"Title:", &config.EditLevel.Title, func(v string) { config.EditLevel.Title = v }},
{"Author:", &config.EditLevel.Author, func(v string) { config.EditLevel.Author = v }},
}
for _, mr := range metaRows {
mr := mr
mrFrame := ui.NewFrame("Metadata " + mr.Label + "Frame")
frame.Pack(mrFrame, ui.Pack{
Side: ui.N,
FillX: true,
PadY: 2,
})
// The label.
mrLabel := ui.NewLabel(ui.Label{
Text: mr.Label,
Font: balance.MenuFont,
})
mrLabel.Configure(ui.Config{
Width: 75,
})
mrFrame.Pack(mrLabel, ui.Pack{
Side: ui.W,
})
// The button.
mrButton := ui.NewButton(mr.Label, ui.NewLabel(ui.Label{
TextVariable: mr.Binding,
Font: balance.MenuFont,
}))
mrButton.Handle(ui.Click, func(ed ui.EventData) error {
shmem.Prompt("Enter a new "+mr.Label, func(answer string) {
if answer != "" {
mr.Update(answer)
}
})
return nil
})
config.Supervisor.Add(mrButton)
mrFrame.Pack(mrButton, ui.Pack{
Side: ui.W,
Expand: true,
PadX: 2,
})
}
}
/****************** /******************
* Confirm/cancel buttons. * Confirm/cancel buttons.
******************/ ******************/