Fix collision detection when an actor's hitbox is offset from 0,0:
* Actors having a hitbox that didn't begin at X,Y 0,0 used to experience
clipping issues with level geometry: because the game tracks their Position
(top left corner of their graphical sprite) and their target position wasn't
being correctly offset by their hitbox offset.
* To resolve the issue, an "ActorOffset" struct is added: you give it the
original game's Actor (with its offset hitbox) and it will record the offset
and give a mocked Actor for collision detection purposes: where the Position
and Target can be offset and where its Hitbox claims to begin at 0,0 matching
its offsetted Position.
* The translation between your original Actor and Offset Actor is handled at the
boundary of the CollidesWithGrid function, so the main algorithm didn't need
to be messed with and the game itself doesn't need to care about the offset.
Make some fixes to the doodad CLI tool:
* Fix palette colors being duplicated/doubled when converting from an image.
* The --palette flag in `doodad convert` now actually functions: so you can
supply an initial palette.json with colors and attributes to e.g. mark which
colors should be solid or fire and give them names. The palette.json doesn't
need to be comprehensive: it will be extended with new distinct colors as
needed during the conversion.
* Fix collision detection to allow actors to walk up slopes smoothly, without
losing any horizontal velocity.
* Fix scrolling a level canvas so that chunks near the right or bottom edge
of the viewpoint were getting culled prematurely.
* Centralize JavaScript exception catching logic to attach Go and JS stack
traces where possible to be more useful for debugging.
* Performance: flush all SDL2 textures from memory between scene transitions
in the app. Also add a `flush-textures` dev console command to flush the
textures at any time - they all should regenerate if still needed based on
underlying go.Images which can be garbage collected.
* Add new pixel attributes: SemiSolid and Slippery (the latter is WIP)
* SemiSolid pixels are only solid below the player character. You can walk on
them and up and down SemiSolid slopes, but can freely pass through from the
sides or jump through from below.
* Update the Palette Editor UI to replace the Attributes buttons: instead of
text labels they now have smaller icons (w/ tooltips) for the Solid,
SemiSolid, Fire, Water and Slippery attributes.
* Bugfix in Palette Editor: use cropped (24x24) images for the Tex buttons so
that the large Bubbles texture stays within its designated space!
* uix.Actor.SetGrounded() to also set the Y velocity to zero when an actor
becomes grounded. This fixes a minor bug where the player's Y velocity (due
to gravity) was not updated while they were grounded, which may eventually
become useful to allow them to jump down thru a SemiSolid floor. Warp Doors
needed a fix to work around the bug, to set the player's Grounded(false) or
else they would hover a few pixels above the ground at their destination,
since Grounded status paused gravity calculations.
Water pixels finally do something other than turn your character blue!
* When the player character is "wet" (touching water pixels, and so appearing in
a blue mask), water physics apply: gravity is slower, your jump height is
halved, but you get infinite jumps to swim higher in the water.
* Holding the jump key under water will incur a short delay between jumps, so
that you don't just fly straight up to the surface. Tap the jump button to
move up quicker, you can spam it all you want.
Azulians are also able to handle being under water:
* They'll sink to the bottom and keep walking back and forth normally.
* If you are above them and noticed, they'll jump (swim) up towards you,
aware of the water and it jumps like you do.
* The Blue Azulian has the poorest vertical aggro range so it isn't a
very good swimmer. The White Azulian is very good at navigating water
as it can pursue the player from the furthest distance of them all.
Changes to the editor:
* New brush pattern added: bubbles.png
* It's the default pattern now for the "water" color of all
of the built-in palettes instead of ink.png
* A repeating pattern of bubbles carved out showing the
level wallpaper.
* The old "Bubbles (circles.png)" is renamed "Circles"
* The last scroll position is saved with the Level file, so when you reload
the level later it's scrolled at where you left it.
* New boolProp to help debug memory issues: eager-render, set it to
false and the loadscreen will not eagerload Go images for all the
level chunks.
* Finally fix the level collision bug where the player could climb walls
to the right.
- 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
* Actors can now walk up gentle inclines to the left as well as they can
to the right. The bug was introduced as a hack to prevent clipping
thru the left wall of a 90 degree corner, but that problem seems
resolved now.
* The F4 key to draw collision boxes works reliably again: it draws the
player's hitbox in world-space using the canvas.DrawStrokes()
function, rather than in screen-space so it follows the player
reliably.
* The F4 key also draws hitboxes for ALL other actors in the level:
buttons, enemies, doors, etc.
* The level geometry collision function is updated to respect a doodad's
declared Hitbox from their script, which may result in a smaller box
than their raw Canvas size. The result is tighter collision between
doodads, and Boy's sprite is rather narrow for its square Canvas so
collision on rightward geometry is tighter for the player character.
* Collision checks between actors also respect the actor's declared
hitboxes now, allowing for Boy to get even closer to a locked door
before being blocked.
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"
There was a clipping bug where the player could sometimes clip thru a
left-side wall, if the left wall and floor made a 90 degree bend and the
player was holding the Left key while jumping slightly into the wall.
A band-aid that seems to work involved two steps:
1. When capping their leftward movement, add a "+ 1" to the cap.
2. At the start of the point loop, enforce the left cap like we do the
ceiling cap.
This seems to patch the problem, BUT it breaks the ability to walk up
slopes while moving left. Right-facing slopes can be climbed fine still.
Note: the original bug never was a problem against right walls, only
left ones, but the true root cause was not identified. See TODO comments
in collide_level.go.
* Player character now experiences acceleration and friction when
walking around the map!
* Actor position and movement had to be converted from int's
(render.Point) to float64's to support fine-grained acceleration
steps.
* Added "physics" package and physics.Vector to be a float64 counterpart
for render.Point. Vector is used for uix.Actor.Position() for the sake
of movement math. Vector is flattened back to a render.Point for
collision purposes, since the levels and hitboxes are pixel-bound.
* Refactor the uix.Actor to no longer extend the doodads.Drawing (so it
can have a Position that's a Vector instead of a Point). This broke
some code that expected `.Doodad` to directly reference the
Drawing.Doodad: now you had to refer to it as `a.Drawing.Doodad` which
was ugly. Added convenience method .Doodad() for a shortcut.
* Moved functions like GetBoundingRect() from doodads package to
collision, where it uses its own slimmer Actor interface for just the
relevant methods it needs.
* Fix the level collision bug that allowed clipping thru a ceiling while
climbing up a wall.
* Fix the scrolling behavior to keep the character on-screen no matter
how fast the character is moving, especially downwards.
* Increase player speed and gravity.
* New cheat: "ghost mode" disables clipping for the player character.
* Mark an actor as "grounded" if they fall and are stopped by the lower
level border, so they may jump again.
Add new doodads:
* Start Flag: place this in a level to set the spawn point of the player
character. If no flag is found, the player spawns at 0,0 in the top
corner of the map. Only use one Start Flag per level, otherwise the
player will randomly spawn at one of them.
* Crumbly Floor: a solid floor that begins to shake and then fall apart
after a moment when a mobile character steps on it. The floor respawns
after 5 seconds.
* State Blocks: blue and orange blocks that toggle between solid and
pass-thru whenever a State Button is activated.
* State Button: a solid "ON/OFF" block that toggles State Blocks back
and forth when touched. Only activates if touched on the side or bottom;
acts as a solid floor when walked on from the top.
New features for doodad scripts:
* Actor scripts: call SetMobile(true) to mark an actor as a mobile mob
(i.e. player character or enemy). Other doodads can check if the actor
colliding with them IsMobile so they don't activate if placed too close
to other (non-mobile) doodads in a level. The Blue and Red Azulians
are the only mobile characters so far.
* Message.Broadcast allows sending a pub/sub message out to ALL doodads
in the level, instead of only to linked doodads as Message.Publish does.
This is used for the State Blocks to globally communicate on/off status
without needing to link them all together manually.
* Improve the collision detection algorithm so that Actor OnCollide
scripts get called more often WHILE an actor is moving, to prevent a
fast-moving actor from zipping right through the "solid" hitbox and
not giving the subject actor time to protest the movement.
* It's implemented by adding a `Settled` boolean to the OnCollide event
object. When the game is testing out movement, Settled=false to give
the actor a chance to say "I'm solid!" and have the moving party be
stopped early.
* After all this is done, for any pair of actors still with overlapping
hitboxes, OnCollide is called one last time with Settled=true. This is
when the actor should run its actions (like publishing messages to
other actors, changing state as in a trapdoor, etc.)
* The new collision detection algorithm works as follows:
* Stage 1 is the same as before, all mobile actors are moved and
tested against level geometry. They record their Original and New
position during this phase.
* Stage 2 is where we re-run that movement but ping actors being
intersected each step of the way. We trace the steps between
Original and New position, test OnCollide handler, and if it returns
false we move the mobile actor to the Last Good Position along the
trace.
* Stage 3 we run the final OnCollide(Settled=true) to let actors run
actions they wanted to for their collide handler, WITHOUT spamming
those actions during Stage 2.
* This should now allow for tweaking of gravity speed and player speed
without breaking all actor collision checking.
* Add initial Ellipse Tool to the Editor Mode. Currently there's
something wrong with the algorithm and the ellipses have a sort of
'lemon shape' to them.
* Refactor the IterLine/IterLine2 functions to be more consistent.
IterLine used to be the raw algorithm that took a bunch of coordinate
numbers and IterLine2 took two render.Point's and was the main one
used throughout the app. Now, IterLine takes the two Points and the
raw algorithm function removed.
* New doodads: Switches.
* They come in four varieties: wall switch (background element, with
"ON/OFF" text) and three side-profile switches for the floor, left
or right walls.
* On collision with the player, they flip their state from "OFF" to
"ON" or vice versa. If the player walks away and then collides
again, the switch flips again.
* Can be used to open/close Electric Doors when turned on/off. Their
default state is "off"
* If a switch receives a power signal from another linked switch, it
sets its own state to match. So, two "on/off" switches that are
connected to a door AND to each other will both flip on/off when one
of them flips.
* Update the Level Collision logic to support Decoration, Fire and Water
pixel collisions.
* Previously, ALL pixels in the level were acting as though solid.
* Non-solid pixels don't count for collision detection, but their
attributes (fire and water) are collected and returned.
* Updated the MenuScene to support loading a map file in Play Mode
instead of Edit Mode. Updated the title screen menu to add a button
for playing levels instead of editing them.
* Wrote some documentation.
* Implement the handler code for `return false` when actors are
colliding with each other and wish to act like solid walls.
* The locked doors will `return false` when they're closed and the
colliding actor does not have the matching key.
* Add arbitrary key/value storage to Actors. The colored keys will set
an actor value "key:%TITLE%" on the one who touched the key before
destroying itself. The colored doors check that key when touched to
decide whether to open.
* The trapdoor now only opens if you're touching it from the top (your
overlap box Y value is 0), but if you touch it from below and the door
is closed, it acts like a solid object.
* Events.OnCollide now receives a CollideEvent object, which makes
available the .Actor who collided and the .Overlap rect which is
zero-relative to the target actor. Doodad scripts can use the .Overlap
to see WHERE in their own box the other actor has intruded.
* Update the LockedDoor and ElectricDoor doodads to detect when the
player has entered their inner rect (since their doors are narrower
than their doodad size)
* Update the Button doodads to only press in when the player actually
touches them (because their sizes are shorter than their doodad
height)
* Update the Trapdoor to only trigger its animation when the board
along its top has been touched, not when the empty space below was
touched from the bottom.
* Events.OnLeave now implemented and fires when an actor who was
previously intersecting your doodad has left.
* The engine detects when an event JS callback returns false.
Eventually, the OnCollide can return false to signify the collision is
not accepted and the actor should be bumped away as if they hit solid
geometry.
* Add sync.WaitGroup to some parts of the level collision detection
function and Canvas.Loop() to speed up the frame rate by load
balancing some work in parallel across multiple cores.
* Improves FPS from 30 to 55+ even for busy scenes with lots of mobile
enemies walking around.
* Before the level collision optimization, framerate would sometimes dip
to 30 FPS simply to move the player character on a completely blank
map!
* CLI: fix the `doodad convert` command to share the same Palette when
converting each frame (layer) of a doodad so subsequent layers find
the correct color swatches for serialization.
* Scripting: add timers and intervals to Doodad scripts to allow them to
animate themselves or add delayed callbacks. The timers have the same
API as a web browser: setTimeout(), setInterval(), clearTimeout(),
clearInterval().
* Add support for uix.Actor to change its currently rendered layer in
the level. For example a Button Doodad can set its image to Layer 1
(pressed) when touched by the player, and Trapdoors can cycle through
their layers to animate opening and closing.
* Usage from a Doodad script: Self.ShowLayer(1)
* Default Doodads: added scripts for all Buttons, Doors, Keys and the
Trapdoor to run their various animations when touched (in the case of
Keys, destroy themselves when touched, because there is no player
inventory yet)
* Move all collision code into the pkg/collision package.
* pkg/doodads/collision.go -> pkg/collision/collide_level.go
* pkg/doodads/collide_actors.go for new Actor collide support
* Add initial collision detection code between actors in Play Mode.