summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--images/catbird/class-diagram.pngbin0 -> 16317 bytes
-rw-r--r--posts/2022-10-25-catbird.md189
2 files changed, 189 insertions, 0 deletions
diff --git a/images/catbird/class-diagram.png b/images/catbird/class-diagram.png
new file mode 100644
index 0000000..f0a747f
--- /dev/null
+++ b/images/catbird/class-diagram.png
Binary files differ
diff --git a/posts/2022-10-25-catbird.md b/posts/2022-10-25-catbird.md
new file mode 100644
index 0000000..28afdbf
--- /dev/null
+++ b/posts/2022-10-25-catbird.md
@@ -0,0 +1,189 @@
+title: Catbird: An experimental game engine for Scheme programmers
+date: 2022-10-25 18:00:00
+tags: guile, gamedev, chickadee, catbird
+summary: An design overview of an experimental Scheme game engine named Catbird.
+---
+
+I've been participating in the Lisp Game Jam for several years now,
+and [the next one is starting on
+10/28](https://itch.io/jam/lisp-game-jam-2022), and with each attempt
+I've been accumulating code that forms something resembling a game
+engine. I'm now attempting to solidify some of the concepts I've been
+exploring in order to make a game engine worthy of releasing in case
+someone else wanted to use it, too. I've named this engine Catbird,
+continuing the bird theme established by my game programming library,
+[Chickadee](/projects/chickadee.html), which the engine is built on.
+
+Game engines are opinionated machines, and no single engine is
+suitable for all types of game or developer. So, the first step for
+designing an engine is to determine *what* kind of games to target and
+*who* might want to use it. I have decided to target small-scale 2D
+games (though I've left the door open for adding 3D support later)
+made by developers who love Emacs, love Scheme, and for whom having a
+pleasant, REPL-driven workflow is more important than raw performance.
+In other words, I've designed it for *myself*, but I know there's a
+small community out there that shares my preferences. It takes a
+billion dollars and a ton of developers to make the Unreal Engine, but
+a small, niche engine can be made by a hobbyist in their spare time.
+
+These are my design goals in more detail:
+
+* REPL-driven development model AKA live coding. This means that
+ everything needs to be modifiable at runtime.
+* Good enough performance for solo developers and small indie teams
+ making games on a small scale. It's okay to use a language
+ implementation with garbage collection!
+* Game objects are composable. Simple objects can be combined to form
+ complex ones.
+* Game objects can run asynchronous scripts.
+* A state machine abstraction breaks down games into small, manageable
+ chunks.
+* A well-defined user input layer cleanly separates game state
+ modifications from the input devices and buttons that trigger them.
+* Linux and MacOS as the initial target platforms. Windows and
+ Android would be nice future additions.
+
+I've taken design inspiration from several places:
+
+* [Godot](https://godotengine.org/), a project that has shown that a
+ FOSS engine can compete with the likes of Unity. If I didn't have
+ such Lispy tendencies and didn't enjoy implementing engine stuff so
+ much, I'd just use Godot. I like their take on the scene node
+ abstraction.
+* [Emacs](https://www.gnu.org/software/emacs/), the extensible,
+ self-documenting text editor and the greatest developer tool ever
+ created. Modifying Emacs at runtime to suit your needs/preferences
+ while you work on your projects was a transcendent experience for me
+ when I first tried Emacs just over a decade ago. Not only do I want
+ to develop games within Emacs, I want my engine to share some of
+ that Emacs spirit.
+* [Xelf](https://gitlab.com/dto/xelf), the Common Lisp game engine
+ that combines concepts from Smalltalk and Emacs with great results.
+ Common Lisp isn't really my thing, but if it's *your* thing then you
+ should really try making a game with it.
+
+In order to implement the design goals, one of the first big decisions
+that needed to be made was about the programming paradigm. Scheme is
+often referred to as a functional language, but it is really a
+multi-paradigm language. Different layers of a program can choose the
+paradigm that bests suits the domain. I decided to copy Xelf and use
+an object oriented architecture using Guile's OOP system: GOOPS.
+GOOPS closely resembles the almighty CLOS: The Common Lisp Object
+System. If you're unfamiliar with CLOS, the most import thing to know
+about it is that methods do not belong to classes. This separation of
+class and method unlocks the ability for methods to dispatch on all of
+their arguments instead of traditional single dispatch on only the
+first argument. Combine that with support for multiple inheritance
+and the metaobject protocol (which I will not go into here, but it
+rocks) and you have an OOP system that I actually enjoy using.
+Classes and methods can be redefined at runtime and it's a much better
+experience than modifying record types (which do not update existing
+instances) and procedures (which often have old versions referenced
+elsewhere that are not updated without re-evaluating that code, too.)
+So, GOOPS provides the flexible foundation for Catbird's REPL-driven
+development model.
+
+Here's a simple diagram of the most important classes in the engine:
+
+![Catbird class diagram](/images/catbird/class-diagram.png)
+
+## Nodes and modes
+
+There are two fundamental types in Catbird, and they rhyme: Nodes and
+modes. Nodes encapsulate objects in the game world, such as the
+player character. Modes encapsulate states of interactivity, such as
+moving the player character around a map with the arrow keys. Catbird
+nodes are similar to Godot nodes, and modes are similar to Emacs
+modes.
+
+Nodes encapsulate the state of an object in the game world. Nodes can
+be rendered, updated, and run asynchronous scripts. Nodes can have
+one parent and zero or more children, forming a tree structure often
+called a scene graph, though in this case it is a true tree because
+nodes cannot belong to more than one parent. This structure allows
+for composing complex objects from simpler ones. A player character
+node might contain an animated sprite node to handle its various
+animations. The state of a parent node affects child nodes. If
+sprite A is at position (2, 3) and contained within sprite B at
+position (3, 5), then sprite A will be rendered at position (5, 8).
+If sprite B is paused, then neither sprite A nor B will have their
+state updated. If sprite B is hidden, then neither sprite A nor B
+will be rendered.
+
+Modes encapsulate pieces of a game's state machine and serve as the
+input handling layer. There are two types of modes: Major and minor.
+Major modes are considered mutually incompatible with each other and
+are used to represent different states within a game scene. For
+example, map traversal in an RPG could be represented by a major mode
+for moving the player character around the map and another major mode
+for interacting with objects/talking to NPCs. Minor modes implement
+smaller, composable pieces of game logic. For example, text field
+editing controls could be contained within a minor mode and composed
+with every major mode that has the player type something. All modes
+have an input map, which translates keyboard/mouse/controller input
+events to calls to their respective event handlers.
+
+## Scenes
+
+Nodes and modes are fundamental but have no relation. Nodes do not
+contain modes, and modes do not contain nodes. A new type is required
+to link the two together: Scenes. In Emacs, buffers contain text. In
+Catbird, scenes contain nodes. Both buffers and scenes have modes.
+
+The scene type is a subclass of node that is used to encapsulate a
+coarse chunk of a game's state machine. For example, an RPG could be
+divided into several scenes: world map, inventory, and battle. Modes
+are attached to scenes to form a playable game. Scenes always have
+one active major mode and zero or more minor modes. Scenes and modes
+together form the state machine abstraction, handling coarse and fine
+grained states, respectively.
+
+## Cameras and regions
+
+Okay, so scenes have nodes, but how is a scene rendered? How do you
+move around within a scene? With a camera, of course! Cameras
+provide a view into a scene. They have a projection, position, and
+orientation. The same scene can be rendered using multiple cameras,
+if desired, such as in a split-screen multiplayer game. This is like
+how an Emacs buffer can be viewed from many different scroll positions
+at the same time.
+
+Cameras need a place to render, and I wanted to make sure that it
+wasn't always assumed that rendering should cover the whole screen, so
+I adapted another Emacs concept and renamed it. Emacs has windows
+(which are *not* desktop windows, Emacs calls those frames because it
+predates windowing systems!) and Catbird has regions. Regions
+represent a sub-section of the game window, defining a viewport to
+which a scene can be rendered. Regions can be associated with one
+scene and one camera. When both a scene and camera are present, the
+scene is rendered to the region's viewport from the perspective of the
+camera. In a typical game, one region that covers the entire window
+is all that's needed. A split-screen multiplayer game, however, could
+divide the window into two regions and render the same scene using
+different cameras. The scene associated with a region can be changed
+to transition from one scene to another.
+
+## Assets
+
+Assets are containers for data that is loaded from the file system,
+such as images or audio files. They are meant to be defined as
+top-level variables in modules and referenced by whichever nodes need
+them. Assets keep track of the file(s) from which the data was
+loaded. Assets are lazy loaded when they are first dereferenced, but
+they can also be manually loaded ahead of time. When developing, the
+files associated with assets are watched for changes. When a change
+is detected in an asset file, the asset is automatically reloaded.
+Nodes keep references to assets, not the data within, so that the
+freshly reloaded data is automatically used by all nodes with a
+reference to that asset. With code and data modifiable at runtime,
+there is rarely a reason to stop and restart the game.
+
+## Conclusion
+
+So yeah, this engine design isn't novel by any means. I'm just
+combining some traditional game engine design with concepts from
+Emacs, some of which have already been applied in engines like Xelf.
+I think the result is quite nice, though, and I don't know of any
+other Scheme project that's quite like it. I will be continuing to
+develop Catbird here, if you want to check out the code:
+<https://git.dthompson.us/catbird.git/>