{"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![Surfaces and edges in a plattform graph](https://s3-us-west-2.amazonaws.com/levi-portfolio-media/surfacer/surfaces-and-edges.png)\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