Week 22: Smooth ends, transient animations, and very stylish code

A screenshot from Squirrel Away showing an exclamation mark above a squirrel which has started a new navigation.
I created a new utility for drawing smooth segments that have two circular ends.
Not as simple as you might think!

tl;dr: I created draw utilities for segments with smooth ends, I created a system for rendering transient animations, and I made a lot of code style fixes.

What happened last week?


  • I created a new custom draw utility for rendering smooth segments that have two circular ends.
    • Sounds simple, but this is actually pretty tricky! Because I had to calculate the points of tangency between two arbitrary circles.
    • The solution is an extension of Thales's theorem.
    • This was pretty typical code for me. I find that I write a lot of code based on either simple trigonometry or basic physics equations of parabolic motion.
    • At this point, I have a lot of custom functions in my toolbox for drawing configurable shapes and handling other geometric tasks.
    • Here's my source code.
A diagram illustrating the angles that define where the points of tangency are which align with the outer tangent lines between the two circles.
This diagram shows the type of shape that I'm rendering. I need to calculate the angle θ in order to know the points of outer tangent lines between the two circles.
  • I created a new system for playing transient animations.
    • These are for animations with known finite durations, which undergo some sort of transition from start to finish.
    • I'm using this for showing a burst animation as the player moves through each beat-indicator hash-mark along a navigation trajectory.
    • And I also refactored my old click and new-nav exclamation-mark animations to use this new transient animation system.
An animated GIF of a recording from Squirrel Away showing the player navigating while the beat hash marks along the navigation trajectory path animate with the beat.
The new transient beat-indicator burst animation.
  • I did some general code style cleanup and submitted Scaffolder and Surfacer to the Godot Asset Library, so other folks can find and use them!
    • The submissions are still pending review.

Laundry list

  • Add support for conditionally handling time-scale (i.e., slow motion) when tracking music beats.
  • Add support for pausing level music when pausing and resuming at the pre-pause position when unpausing.
  • Add a setting for playing metronome tick/tocks in order to help debug Audio scaling.
  • Fix a bug with slow-motion-music, which wasn't taking into account the transition-out tween duration when setting music playback position.
  • Synchronize preselection trajectory pulse animation duration with slow-motion-mode tick beat duration.
  • Create a utility function for drawing a smooth-segment-with-two-circular-ends shape, and create a utility for drawing exclamation marks that uses this other utility.
  • Draw the new-nav exclamation-mark indicator with a custom draw call instead of rendering a poorly scaled font-based symbol.
  • Fix a bug from starting a new slow-motion mode before the previous slow-motion mode had finished transitioning out.
  • Fix draw-utility functions to not show gaps in outlines, by ensuring that polylines start and end in the middle of a segment, which causes the end caps to line up.
  • Fix a rare edge-case bug, where a min-end-velocity calculation could be inconsistent with the corresponding max-end-velocity calculation.
  • Fix a bug where computer-player new-nav exclamation marks weren't showing, except in slow-motion mode.
  • Support only showing the new-nav exclamation mark for computer players.
  • Split apart beat-hash state-calculation logic from drawing logic, and move this calculation logic out of the annotator class.
  • Fix preselection navigation trajectory to update when the player moves as well as when the pointer moves.
  • Update path-preselection beat-hash-marks as time progresses even if the path itself didn't change.
  • Fix bug where previous path beats weren't rendering.
  • Remove "_sec" suffix from time variables, since seconds are the default unit of time across Godot.
  • Implement a new transient-annotator system for conveniently drawing animation items that have a set duration and are then removed.
  • Refactor click annotator and click-nearby-surface annotator to use the new transient-annotator system.
  • Refactor new-nav exclamation mark to use the new transient-annotator system.
  • Create an animation to show when the player passes through nav-trajectory beat hash-marks.
  • Make various cleanups across my codebase to conform to the official GDScript styleguide.
    • Mostly for my own future reference, I'm going to include the commands I used for these automatic changes.
    • Rename signal names to use the past tense.
      • I manually scanned through all signal definitions.
    • Move file-level doc comments down underneath class_name and extends lines.
      • Search: ^(?<![\s\S\r])#
      • I manually fixed these, but it would have been easy to adjust the search/replace commands to handle the swap automatically.
    • Re-order enums, exported variables, and _ready methods.
      • I manually scanned through all enums definitions, exported variables, and _ready methods.
    • Move variable declarations down as close as possible to their points of initialization and use.
      • I mostly had some places that I was declaring variables before defining them.
      • I had to manually fix these.
      • I found these with this search: 
    • Insert extra newlines above function definitions and inline classes and below the class_name/extends header block.
      • Search: (class_name.*\n(?:extends.*\n)?(?:(?:#.*\n)*)?)
      • Replace: $1\n
      • Search: \n((#.*\n)+(static |func |class ))
      • Replace: \n\n$1
    • Rename all files from CamelCase to snake_case.
      • I used a bash command for this.
      • for file in **/* ; do git mv "$file" "$(echo $file|sed -e 's/\([A-Z]\)/_\L\1/g' -e 's/\/_/\//')" ; done
    • Rename all textual file references from CamelCase to snake_case.
      • I used VSCode’s search/replace for this.
      • Search: ([^/"])([A-Z])([a-z0-9_]+)\.(gd|tscn)
      • Replace: $1_\l$2$3.$4
      • Repeat the search/replace until there are no more results (each iteration only handles a single capital letter in a given filename).
  • Re-arrange some utility file structure.
  • Fix a bug with in-air-destination path-optimization that could sometimes delete an intermediate intra-surface edge just before the final surface-to-air edge.
  • Clean-up some warnings in the console.
  • Update READMEs.
  • Submitted Scaffolder and Surfacer to the Godot Asset Library (https://godotengine.org/asset-library/asset).
  • Create a new HTML export.
    • By removing all of the pre-parsed platform-graph files from the build, I was able to fix the OOM errors that were breaking the HTML build before.
  • Toggle beat-tracking logic when the flag is disabled.
  • Move beat-tracking and slow-motion logic from Surfacer to Scaffolder.
  • Consolidate beat-tracking logic into a separate class.

What's next?

  • GMTK 2021 game jam!
    • This is another 48-hour game jam happening next weekend.
  • I think I might use my Surfacer framework for this game jam, so I'll spend some time this week smoothing out rough edges, like making things faster and making the HTML build work better.
  • Also, I might try to implement an idea I have for a more efficient approach to pathfinding.
    • It seems like run-time performance is ok with Surfacer.
    • In contrast, build-time graph calculation is slow, and also platform-graph save-files are very large and hurt performance.
    • I plan to try out a new approach that uses the same expensive build-time graph calculation, but only stores the input instructions and start/end state for each edge. Then, at run time, I'll only calculate the specific trajectory positions/timings when traversing a specific path.
      • This shouldn't affect build time.
      • This should have an immense improvement on file size and runtime memory usage.
      • This should have an impact on runtime performance, since collision calculations will need to be run for each frame of each edge of each path when starting new navigations.
        • But I suspect this will not be too significant.
        • I have already been performing run-time path optimization calculations, which should have essentially the same performance cost as this new approach.

🎉 Cheers!

This is a simple icon representing my sabbatical.