A conversation last summer put a bug in my head for a game design. It's been flitting around for months, and I feel like the idea could be fun for a niche audience. In fact, it's the same idea I wrote about last July, when I tried some exercises from Justin Gary's book. Two or three weeks ago, I got out my paper prototyping supplies and whipped up a minimally playable version. Although the paper version was anemic, it showed that the core ideas could be fun. My intention though had always been for it to be a digital experience, and I knew that a lot of the experimenting would require trying different content and interactions in digital form.
This led to me to explore two different options for digital prototyping. My first option was Flutter, which I continue to be fascinated by. I have used it for several side projects and a few classes, and I am particularly interested in the elegance of Dart and the separation of UI and model-layer concerns. My other option is, of course, Godot Engine, which I have used for most of my game development the past several years. A significant benefit of Godot Engine over Flutter is that I just have a lot of it in my head right now, so I don't have to spend as much time scouring documentation for just the right thing.
The game design itself is, fundamentally, a single-player card game. At this point, I've made several different prototypes in both Flutter and Godot Engine. Indeed, I've made so many, with enough variations on their directory names, that I've had to set up a separate directory just for abandoned prototypes. What I've come to realize just recently, though, is that I have spent a lot of time on the wrong things. To be honest, it's not clear if these things are wrong since I did learn what I wanted to learn from them, but on the other hand, they are wrong because, six months and many hours of labor later, I don't have a version of the game that I can send to my friends to playtest.
In particular, I've been having serial headaches with a fundamental card game interaction problem: I want to drag cards from one area (e.g. a hand of cards) to another. I want this to feel good to the player, so the cards should animate nicely into position. If they are over an area where they make sense to play, the player should get some feedback. If they are dropped in an illegal area, they should go back to where they came from.
Note that none of these issues are core mechanisms: they are UI design problems. They are important UI design problems indeed, and one of my concerns going into the project was that, if I was going to sink personal time into it, I wanted to invest in the right tool stack so I would have to do minimal re-engineering. However, reading Lemarchand's A Playful Production Process—which I am doing in preparation to teach CS390 next semester (blog post coming soon)—got me thinking that I am trying to solve a production problem while I should be focusing on preproduction problems. That is, my goal right now should be to determine if the game should be pursued at all, not whether it should feel good. This means making a toy, a playful artifact, rather than a vertical slice.
Rather than regale you with the chronological narrative of my discoveries, I will summarize some of what I found for each of my engine options.
Flutter:
- The dev tool support here is incredible, with easy refactoring and convenient language constructs.
- The Draggable and DropTarget widgets are really nice for most drag-and-drop purposes, but not for my case. When dragging a widget, a separate element is made for the version that is following the mouse, and it is irrevocably lost on release. This makes it impossible (or at least impracticable) for handling my case where a card animates smoothly into position or returns to the player's hand.
- A custom drag-and-drop system can be built out of Stack, using Positioned widgets. That is, rather than nesting widgets in the normal Flutter way, one can get complete control over an entire canvas of widgets. AnimatedPositioned makes animating these into position a breeze. Indeed, I was pleasantly surprised at how easy this was to express things like, "Keep all my cards in my hand evenly distributed across this widget." The problem is that, when recognizing the drag gesture, the mouse position is consumed by the widget on top: one cannot get mouse entry/exit events for the widgets underneath.
- It seems like, at this point, I'm just building my own UI framework and may as well just be using Flame. The trouble is that then there's yet another piece that I don't know, and development is slow and awkward for me.
- Of course, Godot Engine has the benefit of its visual editor for building scenes, including composing them. This makes it very easy to break up a project into pieces that are individually, but manually, tested.
- Godot Control nodes do have drag-and-drop support, which I had not come across before. Like Flutter, you make a separate view node that is visible just while dragging. It would be great for normal drag-and-drop interfaces, but not for something like a card game.
- Which controls receive mouse focus is again a problem, but it's solved by keeping track of all the geometries in a "tableau" class. It watches for the mouse and, at the level of the table, keeps track of where the mouse is, which areas should be highlighted, when cards should be animated, and so on.
- One of the things I built in a Godot prototype was an EventQueue to manage some of the in-game events, such as waiting to start a round until an animation was finished. That was something I found particularly awkward to do in Flutter. None of my past projects used an EventQueue, and I suspect that if I finish this project, it will need something like that.
- Refactoring in Godot Engine is a huge hassle. It is easy to build a thing. It is almost impossible to rebuild a thing differently.