Hey r/FlutterDev,
I recently released my 4th mobile game using Flutter. This one is a 2D turn-based cyberpunk card game for which I didn't use a specific game engine (such as Flame) outside Flutter.
I just used "complex" UI overlays, tooltips (to guide new users) and responsive widgets. This last part was the most complex since I needed to fit 4 players on mobile.
I'm going to talk about the technical challenges, patterns and lessons learned.
My first version tracked drags in setState, so every pointer move rebuilt the hand and every stack.
Fix: let Draggable do the drag (it moves the feedback in its own overlay, no rebuild loop) and give each card slot its own RepaintBoundary.
The bug that took two days: on small screens I scale the hand down, so the feedback renders smaller, but the built-in drag anchor uses the unscaled layout. The card drifts away from your finger unless you scale the offset:
Offset _dragAnchorStrategy(Draggable<Object> draggable, BuildContext context, Offset position) {
final renderObject = context.findRenderObject();
final RenderBox? box = renderObject is RenderBox ? renderObject : null;
if (box == null) return Offset.zero;
return box.globalToLocal(position) * cardScale;
}
Drop targets accept everything and validate on drop with a reject flash. A cursor that just won't light up feels broken.
Actually, the really first version was tapping on the card and then tapping again on an action, but this was a completely wrong idea because:
A play resolves as: apply effect, animate, pause, check win, advance. I split state by domain and exposed small signals instead of one blob, so a widget only rebuilds for the signals it reads:
Confident-Cabinet688 shares their experience building a 2D turn-based card game using Flutter without traditional game engines like Flame or Unity. They discuss technical challenges, such as handling drag events and state management, and their decision to use Signals over Provider and Supabase over Firebase. They seek feedback on whether using a game engine would have simplified their process and opinions on the use of Signals and flutter_animate.
final Signal<int> currentSeat = signal(0);
final Signal<GamePhase> phase = signal(GamePhase.resolving);
final Signal<int> turnSecondsRemaining = signal(CyberDurations.turnTimeout.inSeconds);
The CPU turn is a chain of Future.delayed steps, so leaving a match mid-think and starting a new one let old callbacks touch the new game. Every async step now checks a generation counter:
if (gen != _generation || isGameOver) return;
Btw, these steps were delayed just to give the player a more realistic experience. Obviously CPU could play in 0.1 seconds.
Talking about the CPU, it enumerates every legal move as a sealed class (CpuInstall, CpuThreat, CpuDefense, CpuHeist, CpuOverride, CpuSwap, CpuInstantProtocol, CpuDiscard) and picks one uniformly at random. That's iteration 1. It loses to anyone paying attention, but it never makes an illegal move, never lags, never costs money. No cloud model: a network round trip mid-turn is a lag spike, it dies offline, and the "thinking" delay is a Future.delayed anyway.
I'm totally fine using AI for most things, but I think it's better to keep traditional logic where we need it.
My earlier apps are Provider + Firebase. Provider is fine, but one ChangeNotifier and a notifyListeners() spray means hand-splitting notifiers to stop full-screen rebuilds. With signals, you declare small, read .value, and only touched signals rebuild. Timer ticks repaint the timer and little else. Downside: smaller ecosystem, and it's easy to over-granulate. I have signals that should be one.
Why not Bloc? Tbh I never used it. I started with Provider and since then it has worked fine. Now I wanted to try something else. Initially I saw in Reddit some conversation around a new package using Bloc + Signals (https://blocsignal.dev/publications/) but it was pretty new so I decided to just use Signals instead.
About Supabase I'd mention kinda the same thing: I started with Firebase and never changed. Now was the time to try something else.
Physics, particles, collision: Flame or Godot. Audio latency too. Rapid SFX with audioplayers need pre-caching, and engines handle that. For turn-based cards with heavy UI, the widget tree was right. The hard parts were sequencing and repaint isolation, not rendering.
... and that's all. Well, not everything, I guess. I'd appreciate some feedback. Do you think all of these would have been easier in Flame or any other? Is Signal a better option here than Providers? Is flutter_animate the best package for comple animations?
Also if you want to provide feedback about the game itself, it's called AI Sabotage: Build the AGI (available on iOS/Android).