Set up the Flutter App and Basic Configuration
This sets up the main function, ensuring the app runs in portrait mode and full-screen mode.
// Step 1: Import essential libraries and set up main entry point
import 'dart:async';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive);
runApp(const MyApp());
}
Create the Main Application Widget
The MyApp widget initializes the app, disabling the debug banner and setting the theme. CarRacingGame is set as the main screen.
// Step 2: Define MyApp, the root widget of the application
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Car Racing Game',
theme: ThemeData(primarySwatch: Colors.blue),
home: const CarRacingGame(),
);
}
}
Define the Main Game Screen
This step initializes variables for the game, including screen dimensions, the player’s car position, obstacle list, and game state.
// Step 3: Create a StatefulWidget for the main game screen
class CarRacingGame extends StatefulWidget {
const CarRacingGame({Key? key}) : super(key: key);
@override
_CarRacingGameState createState() => _CarRacingGameState();
}
class _CarRacingGameState extends State<CarRacingGame> {
late double screenWidth;
late double screenHeight;
late double playerCarX;
late double laneWidth;
List<Map<String, dynamic>> obstacles = [];
Timer? gameTimer;
Timer? obstacleTimer;
bool isGameRunning = false;
int score = 0;
double roadSpeed = 5;
double roadPosition = 0;
final Random random = Random();
@override
void didChangeDependencies() {
super.didChangeDependencies();
screenWidth = MediaQuery.of(context).size.width;
screenHeight = MediaQuery.of(context).size.height;
laneWidth = screenWidth / 3;
playerCarX = screenWidth / 2 - 25; // Center the car initially
}
}
Implement Start and End Game Functions
startGame() initializes game variables, sets a game loop timer, and starts obstacle generation.
endGame() stops the timers and shows the Game Over dialog.
// Step 4: Define functions to start and end the game
void startGame() {
setState(() {
isGameRunning = true;
score = 0;
roadSpeed = 5;
obstacles.clear();
playerCarX = screenWidth / 2 - 25;
});
// Start game and obstacle generation timers
gameTimer = Timer.periodic(const Duration(milliseconds: 16), (timer) {
updateGame();
});
obstacleTimer = Timer.periodic(const Duration(milliseconds: 1500), (timer) {
generateObstacle();
});
}
void endGame() {
setState(() {
isGameRunning = false;
gameTimer?.cancel();
obstacleTimer?.cancel();
});
showGameOverDialog();
}
Show Game Over Dialog
This dialog appears when the game ends, displaying the player’s score and a button to restart.
// Step 5: Create a dialog to show when the game ends
void showGameOverDialog() {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Game Over!'),
content: Text('Your Score: $score'),
actions: [
TextButton(
child: const Text('Play Again'),
onPressed: () {
Navigator.of(context).pop();
startGame();
},
),
],
);
},
);
}
Generate Obstacles and Update Game State
generateObstacle() randomly creates obstacles on the road.
updateGame() manages road and obstacle positions, checks for collisions, and increments the score.
// Step 6: Generate obstacles and update game logic
void generateObstacle() {
if (!isGameRunning) return;
setState(() {
int lane = random.nextInt(3);
obstacles.add({
'x': lane * laneWidth + (laneWidth - 50) / 2,
'y': -100.0,
'width': 50.0,
'height': 80.0,
});
});
}
void updateGame() {
if (!isGameRunning) return;
setState(() {
roadPosition += roadSpeed;
if (roadPosition >= 50) roadPosition = 0;
for (var obstacle in obstacles) {
obstacle['y'] = (obstacle['y'] as double) + roadSpeed;
}
obstacles.removeWhere((obstacle) => obstacle['y'] > screenHeight);
for (var obstacle in obstacles) {
if (checkCollision(
playerCarX,
screenHeight - 150,
50.0,
100.0,
obstacle['x'],
obstacle['y'],
obstacle['width'],
obstacle['height'],
)) {
endGame();
return;
}
}
score++;
if (score % 500 == 0) {
roadSpeed += 0.5;
}
});
}
Implement Collision Detection and Player Movement
checkCollision() determines if the player’s car has hit an obstacle.
movePlayer() updates the car’s horizontal position when the player drags.
// Step 7: Check collisions and move player’s car
bool checkCollision(
double x1, double y1, double w1, double h1, double x2, double y2, double w2, double h2) {
return (x1 < x2 + w2 && x1 + w1 > x2 && y1 < y2 + h2 && y1 + h1 > y2);
}
void movePlayer(double dx) {
if (!isGameRunning) return;
setState(() {
double newX = playerCarX + dx;
if (newX >= 0 && newX <= screenWidth - 50) {
playerCarX = newX;
}
});
}
Build the Game UI and Components
This creates the main game interface, including road lines, player car, obstacles, score, and a start button overlay. The GestureDetector handles player input for movement.
// Step 8: Create the game UI, including player car, obstacles, road lines, and score display
@override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
onHorizontalDragUpdate: (details) {
movePlayer(details.delta.dx);
},
child: Container(
width: screenWidth,
height: screenHeight,
color: Colors.grey[800],
child: Stack(
children: [
// Road lines
...List.generate(20, (index) {
return Positioned(
top: (index * 50 + roadPosition - 50).toDouble(),
left: screenWidth / 3 - 5,
child: Container(width: 10, height: 30, color: Colors.white),
);
}),
...List.generate(20, (index) {
return Positioned(
top: (index * 50 + roadPosition - 50).toDouble(),
left: (screenWidth / 3) * 2 - 5,
child: Container(width: 10, height: 30, color: Colors.white),
);
}),
// Obstacles
...obstacles.map((obstacle) {
return Positioned(
left: obstacle['x'],
top: obstacle['y'],
child: Container(
width: obstacle['width'],
height: obstacle['height'],
color: Colors.blue,
),
);
}).toList(),
// Player car
Positioned(
left: playerCarX,
bottom: 50,
child: Container(
width: 50,
height: 100,
color: Colors.red,
),
),
// Score display
Positioned(
top: 40,
left: 20,
child: Text(
'Score: ${score ~/ 10}',
style: const TextStyle(fontSize: 24, color: Colors.white),
),
),
// Start game overlay
if (!isGameRunning)
Center(
child: ElevatedButton(
onPressed: startGame,
child: const Text('START GAME'),
),
),
],
),
),
),
);
}