- What did Lisp enable you to do well in this entry?
- What challenges did Lisp present in making your entry happen?
- If you are comfortable answering, please you mention how long you have used lisp and describe previous gamedev experience, if any.
Going through all of this really did help cement a lot of the things that I had really not gathered from going through the Phaser tutorials. Basically, all of the challenges I faced with that probably will not be repeated, and I will find its actions much more predictable than I did.
I additionally found out a few lessons about organizing game code. The first, and most important, is to let objects handle their own rendering/positioning, rather than doing it as part of an update loop. That is, when a party member was moved from one hex to another, I had been trying to do the positioning update right then and there. It was vastly easier to just move it to the group and then have the group "fix" its members, and it looked better in the end. It also handled taking members out of the middle of the group better.
The next is the basic way an entity-component system works with immutability. I had made small games
in Clojure before, and knew the necessity of passing a state object to functions. I did not really
get how this "pipeline" method of working and threading state through could be made easier, through
returning an "updated" version of the state object. I really did not "get" the ->
and ->>
threading operators until they become so necessary to avoid writing messy code.
But in a wider sense, reasoning about what would happen on update became easier as I divided up responsibility between namespaces. I now understand that namespaces in Clojure(Script) are somewhat like the equivalent of classes in object-oriented programming languages, just that they are (ideally) limited to functionality rather than also keeping track of data as with a class. So this makes them very much like static-only classes in C#, my normal language.
So all together, if one namespace (system) determines it needs to update an entity, it can delegate the update to a different namespace, and get back the new state object, not needing to know the gory details, and move on. This made a very neat and clean pipeline. The tricky thing is that the phzr/Phaser objects are instead "mutable" rather than immutable. So that became an additional "pipeline" where a phzr object was passed off to a method, that returned the same object after it was updated. Working with those could definitely be cleaner...
Expanding on working with phzr objects, my original intention was to go very deep into splitting out
object into entity components. I would have entities that had separate gameplay components and phzr
object components. For example, an entity would have an ExplorationGroup
component for the
game-level data, and a Group
component for the phzr object. That was probably an abstraction too
far. I eventually needed to do one check on the game-level data, and then another call to get the
phzr object to update it, and was doing this constantly, leading to repetitive code. I started just
including the phzr object as a property of the game-level component, which made for much shorter
code, especially when I figured out how to use map destructuring.
The last thing I feel like I learned is to strongly consider when you actually need to have things
participate as an entity. Many of the components I made ended up being singletons. This added
repetitive code because I would use brute's functions to get all the entities with a specific
component, and then immediately pass it to first
. I didn't write up a function wrapping that up
until very late. Comparatively, I did not place the phzr game object in an entity and just
accessed it through a normal property on the state object. That probably would have made more sense
in the long run for the singletons. Basically, if you may have multiple "instances" of an object,
using the entity system makes sense. But if you know you're going to have only one, ever, make your
life easier and make it visible at the top as a property.
So to sum up:
I do want to finish up the game, though it's going to have vastly different flavor than I originally intended, simply because I don't think it's worth me to dive down too deep into making this the most perfect game I had originally intended. It's just not a story or gameplay experience I care that much about. I'll also probably do some work to pull out the more common code into a template so I can make a Phaser game all that much quicker.
And then, make more even more games!
Sorry for being so late at getting this out, my schedule got a little crazy. You can see my finished code on GitHub, and see how far I got during the jam on the GitHub Pages Site. I still do intend on finishing the game in some state, so I may update it further!
My game idea was new, but based on a riddle I had seen in a TED-Ed video. In that riddle, you are an explorer trying to escape from a temple. There are too many ways to explore by yourself, but luckily you have eight teammates. Unluckily, two of them have been cursed by the temple, and will lie to attempt to lead you the wrong way. You can't tell who's a liar beforehand, you'll need to figure out a way to determine the right path.
My intention was to extend the riddle to multiple iterations, adding more liars as the player got closer to the exit or made mistakes. I decided to change up the flavor slightly, making it about grad students exploring a space-time anomaly. If the player survived, they would be able to find out how exactly the space-time anomaly was created. The characters were going to be rats, because rats are cute!
So in the end, this would be a browser-based game with mouse-based controls (and possibly keyboard controls if I got far enough). I intended to create the art, sounds, and music myself along with the code.
At the end of the jam, I had just started really making progress on the gameplay portion. The code at the end of the jam allowed the player to send out the party members to explore, showed the results of their explorations (with liars possibly lying), and let the party then move through a hard-coded 3-room dungeon I made for testing. There were no penalties applied to the player yet for incorrect choices. I got part of the way through coding up a dialogue system when I realized I was out of time.
My focus on this project was overall very good, it consumed most of my time. Despite not finishing, I got much further along on this game jam than any I've tried in the past. There's an honest seed for a game there that got done. Also, despite my difficulties in deciding how to organize my code, as well as working with Phaser, I know have a much clearer idea, and what I have is definitely something I can use to make the process faster in the future.
In particular, the advice to prioritize things between "now", "soon", "later" was very helpful. I would need them was great advice, because it let me become much harsher about putting things further back in the backlog. I think the vast majority of the stuff I put in the "soon" category on the first day, I never got to, and had I tried to get to them, I would have not made as much progress. It also made it easier for me to push the small rendering bugs I ran into back there, especially as I was going to replace the art any way.
The instant feedback of figwheel to display my code changes was a mixed blessing, but for the most part a benefit. It definitely gave me a system to do debugging without actually using a debugger, so I was able to do all my work in vim and the command-line, using no IDE at all! However, editing features of an IDE like Cursive would have probably been helpful just to speed up coding.
I did not get into really doing gameplay code until the last three days of the jam, and I didn't get to any asset work at all, which was something that really interested me. My main issue was figuring out how best to use phzr and brute together, as well as understanding input.
In particular, I struggled with integrating Phaser's event system, which was based on callbacks with
brute, which ran every frame. Phaser definitely intends for its users to use its callback-based
system. While it offers some functions that can be used to poll, (for example, justPressed
), these
take in a threshold of milliseconds, and will continue to return success throughout that threshold.
I eventually introduced some "blackout" properties on my game state that kept track of when the last
time I had performed a change based on input. Only after enough time had passed since the last
blackout would I perform another change. This seemed to work well enough, but introduced boilerplate
code I was not very happy about. I think in the future I may use ClojureScript's concurrency
features along with the callbacks.
I additionally had a great deal of trouble with positioning sprites. Phaser uses the very common Cartesian coordinate system, but allows sprites to be placed in groups, as well as be the child of other sprites. Within the group, the origin of the coordinate system is moved to the location in the group. This meant that if tried to use the world coordinates, the sprite would not end up where I expected. This also led to problems with how input worked. Dropped sprites would be moved to be over another hex and then considered "dropped" on that hex because of the threshold. The ability to change the "anchor" of a sprite or group made it even worse. In retrospect, it was not that hard of a problem, but I did not have an easy time working with it, and Phaser's documentation does not really describe the system in-depth.
Phaser also has z-level sorting of sprites, but it seems like it will automatically "update" the z scores at times (probably the results of calls I made...). This led to party members, for example, appearing behind hexes, or on top of the results dialog. This came to a head with the group issues mentioned earlier, because I had added my party member sprites to a separate "stage" group that is always above the "world" group, where I had added everything else. I strongly feel that this needs to be better explained within the Phaser documentation.
The last little hurdle that I think is worth mentioning is that Phaser's default units for rotation are radians rather than the more familiar degrees. I later found out there's a way to use degrees, but I spent a lot of time trying to get the mice to rotate towards their eventual destination. This is probably more of a problem of me diving too deep into a problem, though.
OK, I tried to get it all in by the deadline, but despite some great progress, I'm not going to be finished in the next 45 minutes. I could stress myself out, or know when I'm beat. I'll probably take the rest of the weekend to finish it up at least, because I'm about a day out or so.
I now can move through my dungeon, sending out party members to explore, and getting their results, but there is no penalties for doing so. I don't have any other screens done, but am in the middle of adding dialogue trees. Perhaps I could also replace the terrible programmer art I just drew.
I'll at least still upload it to itch when I'm finished.
This was fun, I'll have to do this again!
Streaming in a couple of minutes, finally writing game play code, which I figure is much more interesting than my struggles with Phaser: https://www.twitch.tv/foobardog
I didn't stop working, but I've been heads down on getting input working, so I've been deprioritizing
the dev journals. It's also turned out much harder to get input working. (This is too big for itch.io, so I had to severely edit. If you're really interested, check the full journal on GitHub.)
These days were pretty slow, only got some basic things checked in.
These ended up being very productive days! Basically my problem was that I was trying to use Phaser's event handling system, which depends on callbacks. I didn't see a good way to tie that in with the constant looping method of brute. Phaser callbacks can take additional information, so it would in theory be possible to pass my system object to my callbacks. However, since these callbacks could possibly update the system object, that would introduce an unpredictability of when the object would be updated.
The system object is saved in an atom, so in theory, it would have been referentially OK to modify it whenever. However, I had gone out of my way to make sure that atom was only modified in one place, core.cljs. This means that most of my "update" functions worked on a copy of the system. This is the whole point of ClojureScript's strict requirements on limited mutability (and is why I like it!).
The atom does mean that querying the system object would never be inconsistent, but most of my functions never query it, but a copy instead. This means that an update from a callback could possibly invalidate work going on in another function. It would be possible to coordinate, but it would introduce a lot of complexity, and that's never good!
I instead moved away from Phaser's system and moved towards using the update functions that brute
expects. It ended up working, but it has some downsides. For example, to see if a sprite has been
dropped, you can call the justReleased
method on it. The time that it considers a sprite "just"
released is configurable. However, this is a raw time in milliseconds, not a number of frames. So if
a party member has been dropped, there is a window of time it'll continue to be considered "dropped".
This bit me in the tail when my positioning code was not up to snuff yet. I determine if a party member is dropped on an exploration path if their sprite overlaps the hex sprite. I then wanted to align the party member within a group on that sprite, making the party members on a path well-organized, so that's what I did in my update function. However, with the position being off, sometimes, that would move the sprite to a position where it overlapped a different path.
Since justReleased
still thought the sprite was "just" released, it moved it to that different path.
Which could have done the same thing, and so on. I could modify the threshold for "just", since it was
after all at a hefty 500ms (just over 30 frames at 60 fps) but at a low enough number that could lead
to drops not being recognized, because a long frame could mean that justReleased
returned false
before the function can get to it.
In the end, I didn't really "fix" this problem. I made the groupings on the hexes much closer, so they were unlikely to overlap another sprite. I also had the update function only add the sprite to the group and let the group handle positioning it. This made positioning much more reliable, and had the added effect of groups automatically realigning themselves if a party member was taken out from the middle of the list. But, in theory, this bug still exists.
The other issue at play here is the high mutability of JavaScript and therefore Phaser versus the
low mutability of ClojureScript. Even when using phzr, most of the heavy lifting is done on mutable
objects. ClojureScript handles that idea through the doto
special form, which performs a set of
actions on a presumably mutable object and then returns it. It basically makes ClojureScript act like
an iterative programming language for a bit. However, I confused it with the ->
and ->>
special
forms which allow threading an immutable object through multiple calls. That is, the result of the
first call is fed into the second, which is fed into the third, and so on. It's syntactic sugar for
the normal Lisp pattern of nested calls. However, it ends up looking very similar to doto
, and
I got burnt in a few cases.
ClojureScript also has several different special forms for iteration like for
and doseq
. Similarly,
some of these act in a lazy manner, while others act in an immediate manner. Additionally, both of
those are more oriented towards working on mutable objects. For proper threading of immutable objects,
You pretty much must use loop
and recur
, which are special forms to make recursion easier. But
these also burnt me. Not really the fault of the language, just something I need to get more used to!
At the end of all of this, I was able to drag and drop party members onto exploration paths to add them to groups. It was a frustrating process (I was also working on this on days 4 and 5), but it worked! I could move on to gameplay!
Starting on gameplay, I wanted to first check if I could write a little label on my party members so I could know which ones were which, since I could not remember them by color. This was also a good chance to begin working on bringing fonts, which I knew I would need eventually. Bringing in fonts was actually harder than I thought, but not for any good reason.
I picked out a nice font on Google Fonts that looked like a typewriter font from the era the game is set in. (The equivalent of the 1970s, if you're wondering.) It is a web font, which has the great benefit that the user doesn't have to install it on their computer, nor do I need to have it saved with my game files. The app will download the font at the time it needs it. This is often used on webpages. The downside is that if downloading the font takes too long, you could have a period that text without the font is displayed, and then it "flashes" to the downloaded font. It's annoying on a web page, but in a game like this, it's worse. If the font is not downloaded when you start to create text in Phaser, it'll fall back to another font but you'd have to "update" that text, which is not done "automatically" like it would be on a web page. However, there are solutions.
Google and Typekit have a Web Font Loader JavaScript library that can be used to load web fonts from common font websites and let code know when they've been downloaded. Since it's by Google, it even hooks in well with the Google Closure Library that ClojureScript depends on. The Web Font Loader is available on Google's CDN, which is generally the preferred way to get it. So I tried to add it as a foreign library in my project.clj file through that URL.
However, this seemed to clash with Figwheel, which is sort of a helper for ClojureScript coding that builds the ClojureScript code for you, serves it to you through a local web server, and watches for file updates so it can rebuild the code automatically. This really decreases the length of the code-compile-run cycle. I can just make edits to my ClojureScript source, and in under a minute, I'll see the updated code running in my browser.
For whatever reason, Figwheel was treating the URL to Google's CDN as a local file to be watched, and would crash because Windows knew that wasn't a local file and pushed back. In the end, I just downloaded it, and checked it in to my source tree, which is not preferable. Ideally, you avoid keeping your own copy of an external dependency in your source for many reasons, like dealing with updates to that dependency and legal code redistribution reasons. But, I was already far behind, I didn't need to get bogged down in debugging Figwheel.
Either way, I got the font loaded, wrote the label on the party members, and now, you can drag and drop party members onto hexes. I finished out the day by starting to implement a method for defining the dungeon that the players will traverse through, the first real gameplay code. I'm very hopeful I'll have a full gameplay alpha by the end of Tuesday! Longer than I expected, though. There's no way I'll get in a lot of the nice assets I wanted to add and get gameplay nailed down. But... I learned a bunch, and we'll see what I can do over the next two days. I guess there's technically three, counting the 11th, but I don't think it's in the spirit of the jam to do major work on the 11th. I'll probably consider this a beta, and work on integrating the assets after the Jam is over, basically making a "deluxe" edition.
Whew! A lot to write, and I am probably over-explaining some things to fellow programmers, but I've been linking non-programmers to these journals, so I am assuming very little previous knowledge. Plus, I'm not editing these journals, so I bet I could whittle them down.
Excelsior!
I can't sleep, so I guess I'll write my journal entry.
I sat down to write a draggable component that would expand a sprite so that it could be dragged around
and dropped, which was the last real important “general" functionality that I would need to actually
begin coding up a gameplay alpha. Phaser is made to be straightforward and exposes a large amount of
functionality on the
Sprite
object itself. So to make a sprite draggable, it's actually really easy
in JavaScript:
function createSprite() { var mousey = game.add.sprite(0, 0, 'mouse'); mousey.inputEnabled = true; mousey.input.enableDrag(false, true); // Drag from click-point, bring sprite to top mousey.events.onDragStop.add(checkDestination); } function checkDestination(item) { if (item.x > somethingOrAnother) { item.input.disableDrag(); } else { doSomethingElse(); } }
Properties like
input
and events
refer to specific InputHandler
and Events
classes, which
maintains good code separation.
Similarly, the ClojureScript code is straightforward, once you're used to phzr's paradigms:
(ns example.mousey (:require [phzr.core :as p.core] [phzr.game-object-factory :as p.factory] [pzhr.input-handler :as p.input] [phzr.signal :as p.signal])) (defn create-sprite [game] (let [factory (:add game) mousey (p.factory/sprite factory 0 0 "mouse")] (doto mousey (p.core/pset! :input-enabled true) (-> :input (p.input/enable-drag true false)) (-> :events :on-drag-stop (p.signal/add check-destination))))) (defn check-destination [item] (if (> (:x item) something-or-another) (-> item :input (p.input/disable-drag)) (do-something-else)))
Now, in trying to make my things more Clojurey, I first thought that a
Draggable
component made the most
sense. So an entity with a draggable sprite would have to have two components, a
Sprite
and a Draggable
component. But this introduced dependencies, since Draggable
would need to get the sprite from Sprite
.
It also became hard for me to see how I would be able to toggle a sprite's draggability easily. I would
need to be able to edit the phzr sprite object both during Create and Update. However, under my design,
I didn't keep track of the phzr sprite object after Create. There was no way to get it back during Update.
A darkness warshed over the chimerror…
I began to reconsider the brute demo code. If you look at its component definitions, you'll notice several of them are just empty records. I was confused by this a little. I understood the idea that this was just reifying game concepts as types, but why bother having a record if you're not going to put anything in it?
That's when it hit me. Components were not meant to abstract away engine concepts themselves as
future-proofing. They were meant to
wrap engine manipulation in types that represent game concepts.
For example, the brute demo code defines separate records for
Paddle
, PlayerPaddle
, and CPUPaddle
.
I had wondered why you wouldn't just have one
Paddle
record with differing AI functions, which is a
pattern I've used elsewhere. The reason why you have separate ones is because rather than thinking in an
object-oriented fashion, I should have been thinking with
portals functions.
The other namespaces in the brute demo code did not define entity types, but
systems for manipulating and
managing entities and their components through functions. They have some initialization functions to set
up entities, but the heavy lifting happens in a function called
process-one-game-tick
. That function is
called every game loop. So rather than having each component be aware of what steps it should take during
each stage, you reduce it down to create and update. This gives you a set of namespaces that handle
different
aspects of the game, whether that's engine functionality or a particular type of game entity.
The core namespace just wires it all together.
So realizing brute and entity-component systems were meant to be a sort of aspect-oriented programming,
I sat down to vastly refactor to make my code more match the brute demo code. There's now a
Sprite
and
PartyMember
record. I placed helper functions around input into an fossa.input
namespace. brute shot
back to the front, and I was actually using it to find entities and their components as it had been
designed to do. And at the end of the day… I had a mouse I could drag around the screen with my mouse.
Not exactly how much I had hoped to have done by day three, but I now feel like I'm on my way to grokking both entity-component systems, game engine programming, Clojure, and different programming paradigms. I know me writing about this feels a little arcane, but my experience has been that finding descriptions in English of how to architect your game is very hard. There's a big jump from a basic tutorial to a list of engine functions and specific use cases, and very little writing covering that middle section, which is probably the most important one! It's not just a problem with game programming, it's pretty rampant.
But, onward!
Yay, something that can be played! I'd not worry about the autofire, immediately, going pew, pew, pew, pew is fun!
I did get a little confused with the control scheme, I wasn't expecting the arrow keys to resolve into headings. That is, now, to completely turn clockwise 360 degrees from heading towards the top of the screen, you press right, down, left, up. You basically have to switch to the other keys once that heading is hit. My initial assumption was that I'd just be able to hold right.
Either way, doing good!
I didn't post a Day One update, so I'll take the chance to make an introduction, and then get into the journal
I've long wanted to make small games on the side, and I really love Clojure, so I'm making a game I've codenamed fossa. (Yes, foobardog is me, I've been moving some of my online presence to my new name, chimerror.) I have a title in mind, but I like using a codename first.
My main goal for this jam is to get better at avoiding my major vice of over-engineering and feature creep. I'm a fastidious kitty, and I often deep-dive too often. So the advice on keeping it simple is well taken, I just need the practice.
The basic idea for this game builds off a riddle I saw on TED-Ed they called The Temple Riddle . I've decided to change the flavor and iterate through this riddle a few times to make a longer game of it. Basically, this will be a type of logic game where you need to determine which members of your party are liars so you can escape the temple (well, cross-universe anomaly in my flavor). I may also expand the game by having certain party members having certain tools or abilities that are necessary to explore successfully (like a flashlight, or something).
I'm writing this in ClojureScript using phzr, which are bindings for the Phaser JavaScript library for ClojureScript. I am using brute, an entity-component system library for dependency management purposes. I'm keeping track of tasks and things on this Trello board. When I get to some interesting gameplay coding, I may do some streaming on my Twitch Channel .
So my first task was to figure out phzr and brute. In the pre-work, I managed to implement the Phaser tutorial, which is in JavaScript in ClojureScript. While phzr tries to make things more Clojure-like, that tutorial had me using a lot of global atoms, which I don't think is in the spirit of functional programming, so I was going to pull in brute to manage things in a cleaner way.
I first spun up a basic window, with just a background image and a sprite, using the patterns I had figured out from the pre-work. After that, I began trying to understand how brute could be integrated in. Phaser divides running a game into three stages (well, more than three, but I'm only using three): Preload, Create, and Update. Preload is used to load assets, Create actually creates game entities, and Update is called on every frame of the game loop.
brute is a bit more general than that. You create a system, add entities to that system, and add components to those entities. You then define a chain of system functions (for example, rendering, input, AI) and the system is passed through that chain. Each system function gets the entities and components it needs and handles updating them for their particular purpose.
The
brute demo code defines most of its components as Clojure records, as brute handles keeping track of entities. brute exposes a few methods to get all entities with a particular type of component. For this purpose it uses the ClojureScript type
function. (Well, when running in ClojureScript. For Clojure, it uses the class
function.) Internally, it uses the type information as keys into a map.
So I realized that I would need a way to keep track of which components had actions for which stages. For example, if I was going to have a sprite component, it should make sure to load the image during Preload, and create the sprite during Create. Clojure protocols seemed the perfect abstraction. And it would have worked, perhaps... In Clojure.
The main issue is that ClojureScript does not have as strong run-time reflection support for protocols as Clojure, and that the use of the
type
function didn't really expose what I can do there. The satisfies?
function exists to check protocol satisfaction, but that works on an individual object, not the type information as brute was using for keys. Additionally, it's really a macro and must take a known symbol at compile time. Clojure extends?
is what I would have wanted, but that's not available in ClojureScript.
Day 1 was basically lost figuring all this out. I spent the first half of Day 2 continuing trying to make it work. When deciding to cut my losses, I had the realization that I really was not representing my case accurately. I split the Preload functions into an Asset
protocol, and added an get-assets
to the Component
protocol. I completely ignored the brute way of getting components to just doseq
through all entities. I didn't want to break brute's contract, but I didn't want to completely discard it. I also wanted to add some defensive coding to my processing functions, but I couldn't get it working, and cut my losses. After all, I didn't get into this to write an entity-component system!
So a frustrating two days, but I'm through that. And yes, this was a little bit of ratholing, but I've been able to cut it off way better than I've ever done in the past. Onward to actually interesting parts!