What I Learned
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:
- Understand the ins and outs of your engine
- Delegate very specific code to namespaces handling certain "systems"
- Tie your systems into a pipeline that operate on the game state
- Don't divide things up into components until you're sure you want to
- Don't divide things up into entities until you're sure you want to
What I'm Going to Do Next
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!