Adds `doodad levelpack create` and `doodad levelpack show` commands to
the CLI tool to create levelpacks.
A levelpack is a ZIP file containing a descriptive index.json and
directories for levels and doodads.
* The Red Bird now records its original altitude on the level and will
try and return there should it accidentally climb up or down a wall.
Sometimes goes into a wavy pattern surrounding its original altitude.
* Editor UI: in the default (vertical) toolbar, the Palette now has a
two column view to show more color choices on screen at once.
* User setting added: hide the touch control hints.
* The level scroll logic was getting a null pointer crash if you open a
doodad rather than a level file.
* Add a crosshair option to the level editor, configurable in the Game
Settings window.
- Fix a memory sharing bug in the Giant Screenshot feature.
- Main Menu to eagerload chunks in the background to make scrolling less
jittery. No time for a loadscreen!
- Extra script debugging: names/IDs of doodads are shown when they send
messages to one another.
- Level Properties: you can edit the Bounded max width/height values for
the level.
Doodad changes:
- Buttons: fix a timing bug and keep better track of who is stepping on it,
only popping up when all colliders have left. The effect: they pop up
immediately (not after 200ms) and are more reliable.
- Keys: zero-qty keys will no longer put themselves into the inventory of
characters who already have one except for the player character. So
the Thief will not steal them if she already has the key.
Added to the JavaScript API:
* time.Hour, time.Minute, time.Second, time.Millisecond, time.Microsecond
* Recolor some of the region doodads
* Add command: `doodad edit-level --remove-actor` to remove actors from
your level.
* Tweak the player jump velocity from playtesting levels.
* Tweak max gravity speed to match player max velocity.
* Boy's script watches for his velocity to flip suddenly and stops
animations, limiting the moonwalking a bit.
* JS API: Self.GetVelocity() added.
* 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
* New keybind: 'v' to open a new Viewport in the Level Editor.
* New keybind: Backspace to close the topmost UI window,
and Shift+Backspace to close them all.
* Zoom has graduated out of experimental feature status. Still a bit
buggy but workable.
* Viewport windows now copy the Tool and BrushSize of the toplevel
editor, so drawing in and out of viewports works well.
* Viewport window UI improved: buttons to grow or shrink the window
size, refresh the actors, etc.
Add multi-touch gesture support so that the player can scroll the level
in the editor (and title screen) by treating a two finger swipe to be
equivalent to a middle click drag.
Fun quirks found with SDL2's MultiGestureEvent:
* They don't begin sending us the event until motion is detected after
two fingers have touched the screen; not the moment the second finger
touches it.
* It spams us with events when it detects any tiny change and a lot of
cool details like rotate/pinch deltas, but it never tells us when the
multitouch STOPS! The game has to block left clicks while multitouch
happens so the user doesn't draw all over their level, so it needs to
know when touch has ended.
* The workaround is to track the mouse cursor position at the first
touch and each delta thereafter; if the deltas stop changing tick to
tick, unset the "is touching" variable.
In the level editor, the "Play (P)" button has a new feature: Play
From Here. On mouse down you begin dragging a silhouette of Boy or
whoever the default player character is, as if you were dragging a
doodad onto your level.
Drop the silhouette on your level and enter Play Mode from that
location instead of the Start Flag.
Release your cursor over the Play button or press the "P" key to
spawn at the Start Flag as usual.
In the editor, clicking and dragging with the middle mouse button
will scroll the view of the editor in place of the arrow keys.
When entering Play Mode, the original scroll position in the level
editor is remembered for when you come back - no more having to
scroll from 0,0 each time to get back to where you were working!
The game can now be played using only a touch screen! The left
mouse click (Button1) can now move and control the player
character.
* A box in the very middle of the screen is the "Use" button and
a deadzone for directional inputs.
* Anywhere outside the middle and to the left registers a Left
button, to the right a Right button, above the top of the middle
is a Jump button, and below the bottom of the middle is a down
input (for antigravity mode).
* Tight platforming is possible: above and below the middle box,
the left/right split is tight in the middle of the window. You
can get tight jumps if jumping or go below if you don't want to
jump. The left/right deadzone is only over the space of the Use
button.
If the player is idle for a while with no controller inputs, some
hints will fade in about the touch controls.
Note: the ScrollboxOffset to track the player character is changed
to 60,60 from 60,100 so the camera will track tighter to the player
and so the player will mostly be over the Use button on touch
controls as long as he's away from a level boundary.
In the Level Editor, the "Level->New viewport" menu opens a window with
its own view into your level. You can open as many viewports as you
want.
* Mouse over a viewport and the arrow keys scroll that canvas instead of
the main editor canvas!
* You can draw inside the viewports! A selectbox to choose the tool to
draw with. No palette or thickness support yet!
* The actors are installed as-is when the viewport is created and it
doesn't show any changes to actors after. Make a new viewport for a
refreshed view.
* Strokes committed inside the viewport show up in the main editor (and
in other viewports), and vice versa. The viewports accurately track
changes to the level's colors, just not the actors.
* Fun feature to load a DIFFERENT level inside of the viewport! Editing
that level doesn't save changes or anything.
In the Level Editor, the "Level->Giant Screenshot" menu will take a full
scale PNG screenshot of the entire level, with its wallpaper and
doodads, and save it in ~/.config/doodle/screenshots.
It is currently CPU intensive and slow. With future work it should be
made asynchronous. The function is abstracted away nicely so that the
doodad CLI tool may support this as well.
The Checkpoint Region acts as an invisible checkpoint flag, remembering
the player's location should they need to respawn there.
New cheat: `show all actors` during Play Mode will make every hidden
actor visible. Useful to see your technical doodads during gameplay!
Developer shell: `Execute(command string)` is available to the
JavaScript interpreter. It simulates another command being run on the
developer console.
New category for the Doodad Dropper: "Technical"
Technical doodads have a dashed outline and label for now, and they
turn invisible on level start, and are for hidden technical effects on
your level.
The doodads include:
* Goal Region: acts like an invisible Exit Flag (128x128), the level is
won when the player character touches this region.
* Fire Region: acts like a death barrier (128x128), kills the player
when a generic "You have died!" message.
* Power Source: on level start, acts like a switch and emits a
power(true) signal to all linked doodads. Link it to your Electric
Door for it to be open by default in your level!
* Stall Player (250ms): The player is paused for a moment the first time
it touches this region. Useful to work around timing issues, e.g.
help prevent the player from winning a race against another character.
There are some UI improvements to the Doodad Dropper window:
* If the first page of doodads is short, extra spacers are added so the
alignment and size shows correctly.
* Added a 'background pattern' to the window: any unoccupied icon space
has an inset rectangle slot.
* "Last pages" which are short still render weirdly without reserving
the correct height in the TabFrame.
Doodad scripting engine updates:
* Self.Hide() and Self.Show() available.
* Subscribe to "broadcast:ready" to know when the level is ready, so you
can safely Publish messages without deadlocks!
For levels having a top/left scroll boundary, the top/left point takes
higher priority for resolving out-of-bounds scroll ranges instead of the
bottom/right.
This fixes a bug where you Zoom Out of a level far enough that the
entire boundaries of a Bounded level are smaller than the viewport into
the level. It could happen if playing normal levels in Play Mode on a
very high-resolution monitor. Previously, the level would anchor to the
bottom/right corner of your screen.
With the Zoom In/Out Feature this broke the ability to scroll well on
the level; so the easy fix is to put the X>0, Y>0 bounds check after the
above, so the level will hug the top/left corner of the screen which
fixes both problems.
* If you open a wide unbounded level like Castle.level and zoom out and
scroll left (into negative world coordinates), the level chunks
display correctly now.
* Doodad outline while dragging is now sized properly for the zoom level
* Make doodad hitboxes for Actor/Link Tool more accurate while zoomed
* Fix chunks low on the level not loading while zoomed in
* Fix Link lines drawn between doodads while zoomed - they point to the
correct position and their DrawLine calls have been optimized so they
don't lag out the level when lots of them are drawn at once.
* When the Actor Tool or Link Tool is active, mouse-over hitboxes on the
level's actors now works correctly while zoomed and scrolling in the
level.
* Regression: Level chunks don't appear outside a certain range from
origin while zoomed in.
* Regression: Actors don't draw their sprite while zoomed in, but do
when zoomed out.
Improvements to the Zoom feature:
* Actor position and size within your level scales up and down
appropriately. The canvas size of the actor is scaled and its canvas
is told the Zoom number of the parent so it will render its own
graphic scaled correctly too.
Other features:
* "Experimental" tab added to the Settings window as a UI version of the
--experimental CLI option. The option saves persistently to disk.
* The "Replace Palette" experimental feature now works better. Debating
whether it's a useful feature to even have.
Progress on the Zoom feature: when you zoom in and out, you can draw
shapes accurately onto the level. Seems a little buggy if you edit
while scrolling (as in drawing a very long line).
The title screen buttons are now more colorful.
A new property is added to the Doodad struct: Hitbox (Rect).
The uix.Actor for Play Mode will defer to the Doodad.Hitbox until the
JavaScript has manually set its own via Self.SetHitbox(). So in effect,
scripts no longer need to worry about their hitbox! The one assigned to
the Doodad will be the default.
Scripts can check if their hitbox is zero before setting a default:
if (Self.Hitbox().IsZero()) {
var size = Self.Size() // get doodad canvas size
Self.SetHitbox(0, 0, size, size) // the full square
}
The built-in generic doodad scripts have made this change, so that your
simple doodad can have a custom hitbox defined easily using in-game
tools.
Other changes:
* New script: Generic Collectible Item. Selecting it will add a
"quantity" tag to your doodad, to easily configure the script.
* JavaScript API: "Self.Hitbox()" returns your doodad's current hitbox.
You can check "Self.Hitbox.IsZero()" to check if it's empty.
In the Doodad Properties window, instead of browsing to select a .js
file to install your script, a SelectBox of built-in generic scripts are
available. These scripts implement simple behaviors and adapt to the
full canvas size of the doodad.
Built-in scripts so far include:
* generic-anvil.js: behaves just like the Anvil.
* generic-fire.js: the entire canvas hitbox acts like fire pixels,
"burning" mobile doodads and failing the level for the player.
* generic-solid.js: the entire canvas hitbox acts solid
The Doodad Properties window brings many features that used to be
available only in the `doodad` CLI tool into the Doodad Editor.
* In the Doodad Editor there is a new menubar item: "Doodad" which
corresponds to the "Level" menu when you're editing a level.
* The "Doodad" menu has two items:
- "Doodad Properties" (NEW)
- "Layers" (moved here from the Tools menu)
* The Doodad Properties window lets you edit the Title and Author values
of the doodad, as well as modify its Tags and manage its Script.
* Its script can be attached (browse for .js file on disk), its existing
script saved back to disk (dev shell prompt) or deleted altogether
from the doodad.
* You can create, modify, and delete Tags on the doodad.
Other changes:
* In the Level Editor, the "Level->Page Settings" menu is renamed to
"Level->Level Properties" to match with "Doodad->Doodad Properties"
and the pop-up window is retitled accordingly.
* The Exit Flag only exits if the Player touches it - not just any
mobile doodad!
* New Doodad: Checkpoint Flag. They update the player's spawn point
whenever the player passes one. The most recently activated
checkpoint is rendered brighter than the others.
* End Level Modal: the fake alert box window drawn by the Play Mode
is replaced with a fancy modal widget (similar to Alert and Confirm).
It handles level victory or failure conditions and can show or hide
all the buttons as needed.
* Gameplay: There is a "Retry from Checkpoint" option added, which
appears in the level failure modal. It will teleport you back to
the Start Flag or the last Checkpoint Flag you had touched, without
resetting the level -- your keys, unlocked doors, etc. will be
preserved so you can retry.
* Set a maximum speed on the "Camera Follows Actor" logic of 64
pixels per tick. This results in a smoother scrolling transition
when the player jumps to a new location on the map, such as by
a Warp Door.
* Update the default color palettes:
* All: Add a "hint" magenta color.
* Colored Pencil: Add a "darkstone" solid color.
Updates to the Doodads JavaScript API:
* SetCheckpoint(Point(x, y)): set the player character's spawn
position. Giving it Self.Position() is an easy way to set the
player spawn to your doodad's location.
* Added the Death Barrier to Play Mode to catch players from falling off
the map and then falling indefinitely, especially on Unbounded maps.
* The Death Barrier is set 1,000 pixels below the lowest point on your
map. If the player falls here they get a death message: "Watch out for
falling off the map!"
* Added cheat codes to change the default Player Character doodad, as a
way to force play as a different character (for levels which don't
specify a custom character):
* Play as Bird: fly like a bird
* Play as Blue Azulian: the cell
* Play as Thief: play as thief
* Reset to default (Boy): pinocchio
New feature: link a Start Flag to another doodad in your level
and you will play as that doodad instead of Boy. All Creatures
are designed to be playable. Playing as "other" doodads leads
to interesting effects, like not being able to activate buttons,
switches, or warp doors and not having an inventory to pick up
keys. The Anvil is fun: it can destroy other mobile doodads by
jumping on them.
If the actor does not specify that it has gravity, the gameplay
starts in antigravity mode. This will be the vast majority of
non-mobile doodads and the Bird.
Other changes:
* The Blue and Red Azulians now share a doodad script.
* The Azulians AI is still to walk back and forth, pickup keys and
press buttons. The Blue Azulian walks slower than the red one.
* The Blue Azulian is no longer hidden from the doodads list.
* Actor UUID values in levels are now V1 UUIDs (time-ordered).
This will help to reliably resolve conflicts in draw order
of overlapping doodads (newest added to level wins).
* Link Tool: clicking on a pair of already-linked doodads will
now unlink them, so you don't have to delete one to delete
the link.
* Actor Tool: deleting an actor immediately calls PruneLinks()
to clean up any links that the deleted doodad might have.
This commit adds the Thief character with starter graphics
(no animations).
The Thief walks back and forth and will steal items from other
doodads, including the player. For singleton items that have no
quantity, like the Colored Keys, the Thief will only steal one
if he does not already have it. Quantitied items like the
Small Key are always stolen.
Flexibility in the playable character is introduced: Boy,
Azulian, Bird, and Thief all respond to playable controls.
There is not currently a method to enable these apart from
modifying balance.PlayerCharacterDoodad at compile time.
New and Changed Doodads
* Thief: new doodad that walks back and forth and will steal
items from other characters inventory.
* Bird: has no inventory and cannot pick up items, unless player
controlled. Its hitbox has also been fixed so it collides with
floors correctly - not something normally seen in the Bird.
* Boy: opts in to have inventory.
* Keys (all): only gives themselves to actors having inventories.
JavaScript API - New functions available
* Self.IsPlayer() - returns if the current actor IS the player.
* Self.SetInventory(bool) - doodads must opt-in to having an
inventory. Keys should only give themselves to doodads having
an inventory.
* Self.HasInventory() bool
* Self.AddItem(filename, qty)
* Self.RemoveItem(filename, qty)
* Self.HasItem(filename)
* Self.Inventory() - returns map[string]int
* Self.ClearInventory()
* Self.OnLeave(func(e)) now receives a CollideEvent as parameter
instead of the useless actor ID. Notably, e.Actor is the
leaving actor and e.Settled is always true.
Other Changes
* Play Mode: if playing as a character which doesn't obey gravity,
such as the bird, antigravity controls are enabled by default.
If you `import antigravity` you can turn gravity back on.
* Doodad collision scripts are no longer run in parallel
goroutines. It made the Thief's job difficult trying to steal
items in many threads simultaneously!
* The Anvil doodad is affected by gravity and becomes dangerous when
falling. If it lands on the player character, you die! If it lands on
any other mobile doodad, it destroys it! It can land on solid doodads
such as the Electric Trapdoor and the Crumbly Floor. It will activate
a Crumbly Floor if it lands on one, and can activate buttons and
switches that it passes.
* JavaScript API: FailLevel(message) can be called from a doodad to kill
the player character. The Anvil does this if it collides with the
player while it's been falling.
* New doodad: Electric Trapdoor. It is a horizontal version of the
Electric Door. Opens while powered by a button or a switch and closes
when it loses power.
* The Box doodad will reset to its original location if it receives a
power signal from a linked Button or Switch. So for box pushing
puzzles you can add a reset button in case the boxes get stuck.
* Refactored the Doodad build scripts into many Makefiles for easier
iteration (don't need to compile ALL doodads to test one).
Updates to the JavaScript API for doodads:
* Self.MoveTo(Point) is now available to set the actor's position in
world coordinates.
* Install the new ui.TabFrame widget into the Settings and Doodad
Dropper windows to give them properly tabbed interfaces.
* Doodad Dropper's new tabs divide the list of doodads into categories
to make them easier to find.
* The officially defined categories so far are:
- Objects (Start/End Flags and Box)
- Doors (All locked doors and keys, Warp Doors, and Electric Door)
- Gizmos (All buttons, switches, state blocks/doors, Electric Door)
- Creatures (Blue/Red Azulian, Bird, Boy)
* The "All" tab of the Doodad Dropper will show every doodad regardless
of its category or whether it fit one of the official categories.
* How doodads are assigned categories is by a special "category" tag in
their metadata, e.g. "category=doors,gizmos" - multiple supported.
* The loading screen for Edit and Play modes is stable and the risk of
game crash is removed. The root cause was the setupAsync() functions
running on a background goroutine, and running SDL2 draw functions
while NOT on the main thread, which causes problems.
* The fix is all SDL2 Texture draws become lazy loaded: when the main
thread is presenting, any Wallpaper or ui.Image that has no texture
yet gets one created at that time from the cached image.Image.
* All internal game logic then uses image.Image types, to cache bitmaps
of Level Chunks, Wallpaper images, Sprite icons, etc. and the game is
free to prepare these asynchronously; only the main thread ever
Presents and the SDL2 textures initialize on first appearance.
* Several functions had arguments cleaned up: Canvas.LoadLevel() does
not need the render.Engine as (e.g. wallpaper) textures don't render
at that stage.
* pkg/loadscreen implements a global Loading Screen for loading heavy
levels for playing or editing.
* All chunks in a level are pre-rendered to bitmap before gameplay
begins, which reduces stutter as chunks were being lazily rendered on
first appearance before.
* The loading screen can be played with in the developer console:
$ loadscreen.Show()
$ loadscreen.Hide()
Along with ShowWithProgress(), SetProgress(float64) and IsActive()
* Chunker: separate the concerns between Bitmaps an (SDL2) Textures.
* Chunker.Prerender() converts a chunk to a bitmap (a Go image.Image)
and caches it, only re-rendering if marked as dirty.
* Chunker.Texture() will use the pre-cached bitmap if available to
immediately produce the SDL2 texture.
Other miscellaneous changes:
* Added to the Colored Pencil palette: Sandstone
* Added "perlin noise" brush pattern
Note: this commit introduces instability and crashes:
* New `asyncSetup()` functions run on a goroutine, but SDL2 texture
calls must run on the main thread.
* Chunker avoids this by caching bitmaps, not textures.
* Wallpaper though is unstable, sometimes works, sometimes has graphical
glitches, sometimes crashes the game.
* Wallpaper.Load() and the *Texture() functions are where it crashes.
* Levels and Doodad files will be written in gzip-compressed JSON format
* `boolProp compress-drawings false` to disable compression and save as
classic JSON format directly
* The game can still read uncompressed JSON files
The file size savings on some built-in assets:
* Tutorial 2.level: 2.2M -> 414K (82% smaller)
* warp-door-orange.doodad: 105K -> 17K (84% smaller)
* Migrate off go-bindata to embed built-in fonts, levels and doodads in
favor of Go 1.16 native embed functionality.
* `make bindata` prints a deprecation warning to not break older build
scripts
* Removes all references of bindata from the program
* Holding Shift while pressing arrow keys in the editor will scroll by
just 1 pixel per tick to aid in precise debugging with the Zoom In/Out
feature.
* The keybinds used in canvas_editable.go to catch the arrow keys are
updated to use our nice keybind package. As a consequence, the WASD
keys will also scroll the level.
* The "d for Doodads" keybind is renamed "q" so as not to open the
Doodads window whenever scrolling right using the WASD keys.
WorldIndexAt() translates the pixel below the mouse cursor in screen
space (0,0 at top-left corner of the application window) into a world
coordinate in the level shown inside the canvas, taking into account the
canvas's position on the window and the scroll position.
It now translates correctly when zoom In or Out, so the "Abs:" mouse
position level in the status bar shows correctly.
Zoom features that are still jank:
- Scrolling while zoomed in, the chunks to the top/left start unloading
too rapidly and outpacing the scroll, eventually level is invisible
- Drawing and committing pixels to the image while zoomed in/out is
unpredictable where the pixels actually land.
- Actors in the level don't move or zoom at all.
* Got the level chunks AND the wallpaper to both scale UP and DOWN
consistently together.
* Trying to draw new pixels while zoomed in/out ends up offsetting the
pixels by 2X still. Still seems an issue between screen coordinates
and world coordinates. Zoom in 2X and try and draw a line 64px from
the corners of the screen? The committed line appropriately lands at
the 64px coord on the level data but, zoomed in, it appears 2X to the
right on the screen from where I dropped the cursor!
* When zooming OUT, the limit on number of chunks the viewport will try
and render is not increased, leaving dead space in the screen; more
chunks should render when there's room.
* The Windows build of v0.6.0 couldn't load embedded wallpapers such as
legal.png when asked, but could load the hard-coded default.png
* Root cause was the filesystem.FindFile() checking for path separators
in the filepath, and on Win32 this is \ but the internal wallpaper
paths use /
* Instead of a simple "cur. ver != latest ver" check, parse the Major,
Minor and Patch components and do a detailed check.
* So a x.x.1 release could be made for a specific platform that had a
bad build, and it won't mind when it sees the latest version is the
older x.x.0 build that other platforms had working fine.