Set Up the Main App Entry
Create the entry point of your Flutter app and the main widget that launches the scratch card game screen.
import 'dart:math';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:confetti/confetti.dart';
void main() {
runApp(const ScratchCardGame());
}
class ScratchCardGame extends StatelessWidget {
const ScratchCardGame({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Scratch to Win!',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
fontFamily: 'Roboto',
),
home: const ScratchGameScreen(),
);
}
}
Build the Scratch Game Screen Scaffold
Set up the ScratchGameScreen widget, the main screen where the game will appear. Add the layout and background.
class ScratchGameScreen extends StatefulWidget {
const ScratchGameScreen({Key? key}) : super(key: key);
@override
_ScratchGameScreenState createState() => _ScratchGameScreenState();
}
class _ScratchGameScreenState extends State<ScratchGameScreen>
with SingleTickerProviderStateMixin {
late ConfettiController _confettiController;
final List<String> prizes = ['🎁 Gift Card', '💰 Cash Prize', '🎮 Gaming Console', '🎯 Try Again'];
String? selectedPrize;
bool isRevealed = false;
double scratchProgress = 0.0;
int cardKey = 0;
@override
void initState() {
super.initState();
_confettiController = ConfettiController(duration: const Duration(seconds: 2));
selectedPrize = prizes[Random().nextInt(prizes.length)];
}
@override
void dispose() {
_confettiController.dispose();
super.dispose();
}
void _resetGame() {
setState(() {
isRevealed = false;
scratchProgress = 0.0;
selectedPrize = prizes[Random().nextInt(prizes.length)];
cardKey++;
});
}
}
Add Scratch Progress and Reveal Logic
Create functions to handle scratch card progress and prize reveal.
void _onScratchUpdate(double progress) {
setState(() {
scratchProgress = progress;
if (progress > 0.7 && !isRevealed) {
_revealPrize();
}
});
}
void _revealPrize() {
setState(() {
isRevealed = true;
});
if (selectedPrize != '🎯 Try Again') {
_confettiController.play();
}
}
Build the Main Game Interface
Add the layout to display instructions, the scratch card, and a reset button if the prize is revealed.
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.blue[50],
body: Stack(
children: [
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('Scratch to Win!', style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold, color: Colors.blue)),
const SizedBox(height: 20),
ScratchCard(
key: ValueKey(cardKey),
prize: selectedPrize ?? '',
onScratchUpdate: _onScratchUpdate,
isRevealed: isRevealed,
),
if (isRevealed) ElevatedButton(onPressed: _resetGame, child: const Text('Play Again')),
],
),
),
],
),
);
}
Add Confetti Animation
Add confetti animation to celebrate when the player wins.
And add confetti: ^0.7.0 in your pubspec.yaml file.
Align(
alignment: Alignment.topCenter,
child: ConfettiWidget(
confettiController: _confettiController,
blastDirection: pi / 2,
maxBlastForce: 5,
minBlastForce: 2,
emissionFrequency: 0.05,
numberOfParticles: 50,
gravity: 0.1,
colors: const [Colors.green, Colors.blue, Colors.pink, Colors.orange, Colors.purple],
),
);
Create the Scratch Card Widget
Build the ScratchCard widget that the user can scratch to reveal a prize.
class ScratchCard extends StatefulWidget {
final String prize;
final Function(double) onScratchUpdate;
final bool isRevealed;
const ScratchCard({
Key? key,
required this.prize,
required this.onScratchUpdate,
required this.isRevealed,
}) : super(key: key);
@override
_ScratchCardState createState() => _ScratchCardState();
}
Define the Scratch Effect and Logic
Define how the scratch effect is implemented, handling touch events and updating scratch progress.
class _ScratchCardState extends State<ScratchCard> {
final _scratchKey = GlobalKey();
final List<Offset> _scratchPoints = [];
ui.Image? _scratchImage;
bool _isLoading = true;
void _onPanUpdate(DragUpdateDetails details) {
if (widget.isRevealed) return;
final RenderBox renderBox = _scratchKey.currentContext!.findRenderObject() as RenderBox;
final position = renderBox.globalToLocal(details.globalPosition);
setState(() {
_scratchPoints.add(position);
});
widget.onScratchUpdate(_scratchPoints.length / 1200);
}
}
Customize Scratch Effect with Custom Painter
Define a ScratchPainter to apply a scratchable overlay effect on the card.
class ScratchPainter extends CustomPainter {
final List<Offset> points;
final ui.Image scratchImage;
ScratchPainter({required this.points, required this.scratchImage});
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()..blendMode = BlendMode.clear;
canvas.drawImage(scratchImage, Offset.zero, Paint());
for (var point in points) {
canvas.drawCircle(point, 20, paint);
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}