{"collectionMetadata":{"baseUrl":"https://s3-us-west-2.amazonaws.com/levi-portfolio-media","thumbnailName":"thumbnail.png","thumbnailDimensions":{"width":128,"height":128},"logoName":"logo.png"},"posts":[{"id":"about-levi","titleShort":"About
Levi\n(and other links)","titleLong":"About
Levi","emphasis":1,"color":"#bc8e62","urls":{"devlog":"https://devlog.levi.dev","githubProfile":"https://github.com/levilindsey","facebook":"https://facebook.com/levislindsey","twitter":"https://twitter.com/levisl","linkedin":"https://linkedin.com/in/levi-lindsey","youtube":"https://www.youtube.com/playlist?list=PLIuJN99AFOPSF3p4f0siFo22XiMXqijQU","soundcloud":"https://soundcloud.com/levilindsey","itchio":"https://levilindsey.itch.io/","snoring-cat":"https://snoringcat.games","resume":"https://levi.dev/resume"},"jobTitle":"Gameplay
Engineer","location":"Seattle,
WA","date":{"start":"4/27/1988","end":"Present","tieBreaker":100},"categories":[],"images":[{"fileName":"levi-and-kid.jpg","description":"levi
with his daughter."},{"fileName":"levi-in-savitri.jpg","description":"Levi playing
Satyavan in the opera \"Savitri\" by Gustav Holst. In this scene, Satyavan is warning off
whomever is lurking in the
forest."},{"fileName":"jackie-and-levi-in-maui.jpg","description":"Levi with his wife,
Jackie, in Maui."},{"fileName":"roydor-commission-levi-solo-480.png","description":"A
pixel-art avatar a friend commissioned for
Levi."},{"fileName":"levi-at-forge.jpg","description":"Levi designed and built a
four-burner, propane-powered forge for his
father."},{"fileName":"levi-in-the-bartered-bride.jpg","description":"Levi playing
Vašek in the opera \"The Bartered Bride\" by Bedřich Smetana. In this scene,
Vašek, a stuttering fool, is musing about about his forthcoming arranged
marriage."},{"fileName":"jackie-and-levi-wedding-toast.jpg","description":"Levi and Jackie
at their wedding."},{"fileName":"levi-at-camp-hahobas.jpg","description":"Levi leading a
campfire song about peeling a banana. He was a counselor and Life Guard at Camp Hahobas, a
Boy Scout summer camp."},{"fileName":"levi-head-shot.jpg","description":"Levi at the
University of Washington's Friday Harbor Laboratories on San Juan
Island."}],"videos":[],"content":"_\"I write code. I sing songs. I create
things.\"_\r\n\r\n## The Bio\r\n\r\nLevi is a Gameplay Engineer. He used to be a Frontend
(Web) Software Engineer. He's also constantly tinkering on game-dev side
projects—which are all highlighted in this portfolio.\r\n\r\nLevi is originally from
Olympia, WA. He was in California for a bit, and he now lives in Seattle, WA with his wife
Jackie and their daughter.\r\n\r\nOutside the realm of computer science, Levi has many
hobbies and interests. First and foremost, he is a musician. Levi is a classically trained
singer and has led in many operas and musicals; some of his more notable performances
include: [Dido and Aeneas][dido-and-aeneas-url] (Aeneas), [Savitri][savitri-url]
(Satyavan), and [My Fair Lady][my-fair-lady-url] (Henry Higgins). Levi also [sings with a
capella groups][love-like-you-url] and [plays a lot of ukulele][down-today-url]. Check out
some of his [recordings on YouTube][youtube-url]!\r\n\r\nSome of his other hobbies
include: board games/card games, laser-cutting, brewing beer, blacksmithing, scuba diving,
gardening, baking, and a never-ending list of home improvements.\r\n\r\n## The
Cover-Letter Schpiel\r\n\r\nLevi is a seasoned technical leader with experience guiding
projects through all stages of development and across a wide array of platforms.\r\n\r\n-
He invents novel interactive experiences.\r\n- He develops with an emphasis on
maintainable and self-documented code.\r\n- He designs maintainable solutions to
high-level problems, in ambiguous problem spaces, with complicated dependencies.\r\n- He
leads teams, designs technical roadmaps, and coordinates timelines and dependencies.\r\n-
He advocates open-source technology. You can fork all of his many side-projects at
[github.com/levilindsey](https://github.com/levilindsey)! \r\n\r\n\r\n[github-url]:
https://github.com/levilindsey\r\n[google-url]: https://google.com/about\r\n[gcp-url]:
https://cloud.google.com\r\n[jackie-url]:
http://www.jackieandlevi.com/jackie\r\n[dido-and-aeneas-url]:
https://en.wikipedia.org/wiki/Dido_and_Aeneas\r\n[savitri-url]:
https://en.wikipedia.org/wiki/Savitri_(opera)\r\n[my-fair-lady-url]:
https://en.wikipedia.org/wiki/My_Fair_Lady\r\n[youtube-url]:
https://www.youtube.com/playlist?list=PLIuJN99AFOPSF3p4f0siFo22XiMXqijQU\r\n[love-like-you-url]:
https://www.youtube.com/watch?v=yH7L_bZSwbM&list=PLIuJN99AFOPSF3p4f0siFo22XiMXqijQU\r\n[down-today-url]:
https://youtu.be/HmALRuBoDno\r\n[camp-hahobas-url]:
https://web.archive.org/web/20160807121041/http://www.hahobas.org/\r\n"},{"id":"manticore","titleShort":"Manticore\nGames","titleLong":"Manticore
Games","emphasis":1,"color":"#ffb116","urls":{"homepage":"https://www.manticoregames.com/"},"jobTitle":"Senior
Software Engineer -
Gameplay","location":"Remote","date":{"start":"4/2023","end":"Present","tieBreaker":30},"categories":["Work","C++","Unreal","Game","3D","Teamwork"],"images":[],"videos":[],"content":"_[Manticore
Games](https://www.manticoregames.com/) is a video game company based in San Mateo,
California._\r\n\r\nLevi is a Senior Software Engineer working on
gameplay.\r\n"},{"id":"devlog","titleShort":"Devlog","titleLong":"Devlog","emphasis":0.9,"color":"#ffe642","urls":{"devlog":"https://devlog.levi.dev"},"jobTitle":"","location":"","date":{"start":"1/2021","end":"Present","tieBreaker":18},"categories":["AI","Art","Animation","Music","Godot","Game","2D","Solo-Work"],"images":[],"videos":[],"content":"Levi
originally used this devlog to publish weekly updates on his progress during his
year-and-a-half-long game-dev sabbatical.\r\n\r\nHe now uses it for less-frequent updates
on recent game jams and his overall career.\r\n\r\n- During his sabbatical Levi published
more than 1.5 posts per week.\r\n- His devlog has received more than 26,000 views (as of
January 2023).\r\n- His devlog includes more than 17 deep-dive and tutorial posts
([levi.dev/game-dev-tutorials](levi.dev/game-dev-tutorials)).\r\n - In particular, a
6-part tutorial series on creating platformer AIs
([levi.dev/platformer-ai-tutorial](https://levi.dev/platformer-ai-tutorial)).
\r\n"},{"id":"surfacer","titleShort":"Platformer\nprocedural\npathfinding","titleLong":"Surfacer:
A procedural pathfinding 2D-platformer framework for
Godot","emphasis":1,"color":"#4488c0","urls":{"demo":"https://snoringcat.games/play/squirrel-away","github":"https://github.com/SnoringCatGames/surfacer","godot-asset-library":"https://godotengine.org/asset-library/asset/968"},"jobTitle":"","location":"","date":{"start":"2/2019","end":"Present","tieBreaker":15},"categories":["Side-Project","App","Godot","Game","AI","2D","Library"],"images":[{"fileName":"surfaces-and-edges.png","description":"The
Surfacer framework works pre-parsing a level into a \"platform graph\". The nodes are
represented by points along the different surfaces in the level (floors, walls, and
ceilings). The edges are represented by possible movement trajectories between points
along surfaces."},{"fileName":"navigator-preselection.png","description":"A* search is
used to find paths through the platform
graph."},{"fileName":"edge-step-calculation-debugging.png","description":"Surfacer
includes a powerful platform graph inspector, which makes it easy to understand and debug
how the platform graph was
calculated."}],"videos":[{"videoHost":"youtube","id":"2Q15fjAEncg","description":"A
demonstration of the Surfacer framework in action. A cat is controlled by mouse clicks to
navigate through a level of 2D platforms."}],"content":"_Point-and-click
platformers!_\r\n\r\n_An AI and pathfinding 2D-platformer framework for
[Godot](https://godotengine.org/)._\r\n\r\n_\"Surfacer\": Like a platformer, but with
walking, climbing, and jumping on all surfaces!_\r\n\r\n\r\n\r\n**tl;dr**:
Surfacer works by **pre-parsing** a level into a **\"platform graph\"**. The **nodes** are
represented by points along the different surfaces in the level (floors, walls, and
ceilings). The **edges** are represented by possible movement trajectories between points
along surfaces. There are different types of edges for different types of movement (e.g.,
jumping from a floor to a floor, falling from a wall, walking along a floor). At run time,
**[A* search](https://en.wikipedia.org/wiki/A*_search_algorithm)** is used to calculate a
path to a given destination.\r\n\r\nSome features include:\r\n- Surfacer includes a
powerful **character-behavior system** for easily creating a character AI with high-level
behaviors like \"wander\", \"follow\", \"run-away\", \"return\".\r\n- Easy-to-use
point-and-click navigation for player-controlled characters.\r\n- [Configurable movement
parameters](https://github.com/SnoringCatGames/surfacer/blob/master/src/platform_graph/edge/models/movement_params.gd)
on a per-character basis (e.g., horizontal acceleration, jump power, gravity, collision
boundary shape and size, which types of edge movement are allowed).\r\n- Level creation
using Godot's standard pattern with a [TileMap in the 2D scene
editor](https://docs.godotengine.org/en/3.2/tutorials/2d/using_tilemaps.html).\r\n-
Preparsing the level into a platform graph, and using A* search for efficient path-finding
at runtime.\r\n- A powerful inspector for analyzing the platform graph, in order to debug
and better understand how edges were calculated.\r\n- Walking on floors, climbing on
walls, climbing on ceilings, jumping and falling from anywhere.\r\n- [Variable-height jump
and
fast-fall](https://kotaku.com/the-mechanics-behind-satisfying-2d-jumping-1761940693).\r\n-
Adjusting movement trajectories to move around intermediate surfaces (such as jumping over
a wall or around a floor).\r\n\r\nSee [more details in the library's
README](https://github.com/SnoringCatGames/surfacer).\r\n\r\nYou can also read more about
how Surfacer's AI and movement works in **[this series of devlog posts on
devlog.levi.dev](https://devlog.levi.dev/2021/09/building-platformer-ai-from-low-level.html)**.\r\n\r\n>
_Surfacer is owned by [Snoring Cat
LLC](https://snoringcat.games)._"},{"id":"scaffolder","titleShort":"Godot\napplication\nscaffolding","titleLong":"Scaffolder:
Application scaffolding and utility functionality for
Godot","emphasis":1,"color":"#4488c0","urls":{"github":"https://github.com/SnoringCatGames/scaffolder","godot-asset-library":"https://godotengine.org/asset-library/asset/969"},"jobTitle":"","location":"","date":{"start":"11/2020","end":"Present","tieBreaker":12},"categories":["Side-Project","App","Godot","Game","2D","Library"],"images":[{"fileName":"scaffolder-gui.gif","description":"Scaffolder
provides a bunch of general-purpose UI and application scaffolding, such as a widget
library and a a screen layout and navigation
system."},{"fileName":"scaffolder-resizing.gif","description":"Scaffolder provides a
sophisticated rescaling system to help you make responsive
UIs."}],"videos":[],"content":"This is an opinionated framework that provides a bunch of
general-purpose application scaffolding and utility functionality for Godot
games.\r\n\r\n## Features\r\n\r\n### Viewport scaling\r\n\r\nThis framework handles
viewport scaling directly. You will need to turn off Godot's built-in viewport scaling
(`Display > Window > Stretch > Mode = disabled`).\r\n\r\nThis provides some powerful
benefits over Godot's standard behaviors, but requires you to be careful with how you
define your GUI layouts.\r\n\r\n#### Handling camera zoom\r\n\r\nThis provides limited
flexibility in how far the camera is zoomed. That is, you will be able to see more of the
level on a larger screen, but not too much more of the level. Similarly, on a wider
screen, you will be able to able to see more from side to side, but not too much
more.\r\n\r\n- You can configure a minimum and maximum aspect ratio for the game
region.\r\n- You can configure a default screen size and aspect ratio that the levels are
designed around.\r\n- At runtime, if the current viewport aspect ratio is greater than the
max or less than the min, bars will be shown along the sides or top and bottom of the game
area.\r\n- At runtime, the camera zoom will be adjusted so that the same amount of level
is showing, either vertically or horizontally, as would be visible with the configured
default screen size. If the screen aspect ratio is different than the default, then a
little more of the level is visible in the other direction.\r\n- Any annotations that are
drawn in the separate annotations CanvasLayer are automatically transformed to match
whatever the game-area's current zoom and position is.\r\n- Click positions can also be
transformed to match the game area.\r\n\r\n#### Handling GUI scale\r\n\r\n- At runtime, a
`gui_scale` value is calculated according to how the current screen resolution compares to
the expected default screen resolution, as described above.\r\n- Then all fonts—which are
registered with the scaffold configuration—are resized according to this `gui_scale`.\r\n-
Then the size, position, and scale of all GUI nodes are updated accordingly.\r\n\r\n####
Constraints for how you define your GUI layouts\r\n\r\n> TODO: List any other
constraints/tips.\r\n\r\n- Avoid custom positions, except maybe for centering images. For
example:\r\n - Instead of encoding a margin/offset, use a VBoxContainer or HBoxContainer
parent, and include an empty spacer sibling with size or min-size.\r\n - This is
especially important when your positioning is calculated to include bottom/right-side
margins.\r\n- Centering images:\r\n - To center an image, I often place a `TextureRect`
inside of a `Control` inside of some time of auto-positioning container.\r\n - I then set
the image position in this way: `TextureRect.rect_position =
-TextureRect.rect_size/2`.\r\n - This wrapper pattern also works well when I need to scale
the image.\r\n- In general, whenever possible, I find it helpful to use a VBoxContainer or
HBoxContainer as a parent, and to have children use the shrink-center size flag for both
horizontal and vertical directions along with a min-size.\r\n\r\n### Analytics\r\n\r\nThis
feature depends on the proprietary third-party **[Google
Analytics](https://analytics.google.com/analytics/web/#/)** service.\r\n\r\n- Fortunately,
Google Analytics is at least free to use.\r\n- To get started with Google Analytics, [read
this doc](https://support.google.com/analytics/answer/1008015?hl=en).\r\n- To learn more
about the \"Measurement Protocol\" API that this class uses to send event info, [read this
doc](https://developers.google.com/analytics/devguides/collection/protocol/v1).\r\n- To
learn more about the \"Reporting API\" you could use to run arbitrary queries on your
recorded analytics, [read this
doc](https://developers.google.com/analytics/devguides/reporting/core/v4).\r\n -
Alternatively, you could just use [Google's convenient web
client](http://analytics.google.com/).\r\n\r\n#### \"Privacy Policy\" and \"Terms and
Conditions\" documents\r\n\r\nIf you intend to record any sort of user data (including
app-usage analytics or crash logs), you should create a \"Privacy Policy\" document and a
\"Terms and Conditions\" document. These are often legally required when recording any
sort of app-usage data. Fortunately, there are a lot of tools out there to help you easily
generate these documents. You could then easily host these as view-only [Google
Docs](https://docs.google.com/).\r\n\r\nHere are two such generator tools that might be
useful, and at least have free-trial options:\r\n- [Termly's privacy policy
generator](https://termly.io/products/privacy-policy-generator/?ftseo)\r\n- [Nishant's
terms and conditions
generator](https://app-privacy-policy-generator.nisrulz.com/)\r\n\r\n> _**DISCLAIMER:**
I'm not a lawyer, so don't interpret anything from this framework as legal advice, and
make sure you understand which laws you need to obey._\r\n\r\n### Automatic error/crash
reporting\r\n\r\nThis feature currently depends on the proprietary third-party **[Google
Cloud Storage](https://cloud.google.com/storage)** service. But you could easily override
it to upload logs somewhere else.\r\n\r\n### Screen layout and navigation\r\n\r\n- You can
control transitions through `Gs.nav`.\r\n- It is easy to include custom screens and
exclude default screens.\r\n- Here are some of the default screns included:\r\n - Main
menu\r\n - Credits\r\n - Settings\r\n - Configurable to display checkboxes, dropdowns, or
plain text for whatever settings you might want to support.\r\n - Level select\r\n -
Game/level\r\n - Pause\r\n - Notification\r\n - Configurable to display custom text and
buttons as needed.\r\n - Game over\r\n\r\n### Lots of useful utility functions\r\n\r\nIt
might just be easiest to scroll through some of the following files to see what sorts of
functions are included:\r\n-
[`Audio`](https://github.com/SnoringCatGames/scaffolder/blob/master/src/utils/Audio.gd)\r\n-
[`CameraShake`](https://github.com/SnoringCatGames/scaffolder/blob/master/src/utils/CameraShake.gd)\r\n-
[`DrawUtils`](https://github.com/SnoringCatGames/scaffolder/blob/master/src/utils/DrawUtils.gd)\r\n-
[`Geometry`](https://github.com/SnoringCatGames/scaffolder/blob/master/src/utils/Geometry.gd)\r\n-
[`Profiler`](https://github.com/SnoringCatGames/scaffolder/blob/master/src/utils/Profiler.gd)\r\n-
[`SaveState`](https://github.com/SnoringCatGames/scaffolder/blob/master/src/data/SaveState.gd)\r\n-
[`Time`](https://github.com/SnoringCatGames/scaffolder/blob/master/src/utils/Time.gd)\r\n-
[`Utils`](https://github.com/SnoringCatGames/scaffolder/blob/master/src/utils/Utils.gd)\r\n\r\n###
A widget library\r\n\r\nFor example:\r\n-
[`AccordionPanel`](https://github.com/SnoringCatGames/scaffolder/blob/master/src/gui/AccordionPanel.gd)\r\n-
[`LabeledControlList`](https://github.com/SnoringCatGames/scaffolder/blob/master/src/gui/labeled_control_list/LabeledControlList.gd)\r\n-
[`ShinyButton`](https://github.com/SnoringCatGames/scaffolder/blob/master/src/gui/ShinyButton.gd)\r\n-
[`NavBar`](https://github.com/SnoringCatGames/scaffolder/blob/master/src/gui/NavBar.gd)\r\n\r\n>
_Scaffolder is owned by [Snoring Cat
LLC](https://snoringcat.games)._\r\n"},{"id":"snoring-cat","titleShort":"Snoring Cat
LLC","titleLong":"Snoring Cat
LLC","emphasis":0.6,"color":"#7e664d","urls":{"snoring-cat":"https://snoringcat.games"},"jobTitle":"Owner
and manager","location":"Seattle,
WA","date":{"start":"2/2021","end":"Present","tieBreaker":9},"categories":["Work","Art","Animation","Music","Godot","Game","2D","Solo-Work"],"images":[{"fileName":"icon-512.png","description":"A
pixel-art sleeping cat."}],"videos":[],"content":"Levi formed Snoring Cat LLC to publish
some of his more substantial games and software projects under.\r\n\r\nCheck out Snoring
Cat LLC's site at [snoringcat.games](https://snoringcat.games).\r\n\r\nIf you're curious
about what-all went into forming this LLC, check out [this devlog
post](https://devlog.levi.dev/2021/02/snoring-cat-forming-llc.html).\r\n"},{"id":"Music","titleShort":"Music:\nPerformances
&\ncompositions","titleLong":"Music composition and
performance","emphasis":0.6,"color":"#f774ff","urls":{"youtube":"https://www.youtube.com/playlist?list=PLIuJN99AFOPRwoYLk6O_j8kPK6UtWQTIk","soundcloud":"https://soundcloud.com/levilindsey"},"jobTitle":"Singer,
arranger,
composer","location":"","date":{"start":"4/27/1988","end":"Present","tieBreaker":3},"categories":["Music"],"images":[],"videos":[],"content":"Levi
has an extensive musical background.\r\n\r\n* He has been a singer all his life, and he's
played [various
instruments](https://www.youtube.com/playlist?list=PLIuJN99AFOPTzVmY-g6RAwP9CAIIa-PtS)
along the way.\r\n* He has a Bachelor of Music in Vocal Performance from the University of
Washington.\r\n* He's sung in many operas and musicals, including **Dido and Aeneas**
(Aeneas), **Savitri** (Satyavan), and **My Fair Lady** (Henry Higgins).\r\n* He's sung in
dozens of [choirs and a capella
groups](https://www.youtube.com/playlist?list=PLIuJN99AFOPRl5MH9GwFQWpPEP3zkPLBs).\r\n* In
recent years, he's also been [composing
music](https://soundcloud.com/levilindsey/sets/chiptunes) (mostly chiptunes for his game
projects).\r\n\r\nYou can listen to many of Levi's
[compositions](https://soundcloud.com/levilindsey/sets/chiptunes) and
[performances](https://youtube.com/playlist?list=PLIuJN99AFOPSF3p4f0siFo22XiMXqijQU) on
YouTube and SoundCloud.\r\n\r\n \r\n