Adding Flame

This commit is contained in:
Alex Vasilev 2024-08-02 13:53:40 +03:00
parent 909d3d9577
commit f2fe5dcd93
14 changed files with 174 additions and 102 deletions

View File

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

View File

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

View File

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

View File

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

View File

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

View File

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 77 KiB

View File

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

View File

@ -0,0 +1,14 @@
import 'package:flame/flame.dart';
Future<void> loadImages() async {
final imageNames = [
'crystal1.png',
'crystal2.png',
'crystal3.png',
'crystal4.png',
'crystal5.png',
'crystal6.png',
'crystal7.png',
];
await Future.wait(imageNames.map((name) => Flame.images.load(name)));
}

View File

@ -0,0 +1,117 @@
import 'package:flame/components.dart';
import 'package:flame/events.dart';
import 'package:flame/flame.dart';
import 'package:flame/game.dart';
import 'package:match_magic/models/game_state.dart';
import 'package:flutter/material.dart';
import 'dart:ui';
class MatchMagicGame extends FlameGame with TapDetector {
final GameState gameState;
MatchMagicGame(this.gameState);
late double tileSize;
late double gridWidth;
late double gridHeight;
late double gridOffsetX;
late double gridOffsetY;
final Map<String, Sprite> sprites = {};
late SpriteComponent selectedTile;
bool hasSelectedTile = false;
final selectedPaint = Paint()..color = Colors.white.withOpacity(0.5);
@override
Future<void> onLoad() async {
await super.onLoad();
await loadImages();
loadGrid();
}
Future<void> loadImages() async {
final imageNames = [
'crystal1.png',
'crystal2.png',
'crystal3.png',
'crystal4.png',
'crystal5.png',
'crystal6.png',
'crystal0.png',
];
for (var name in imageNames) {
final sprite = Sprite(await Flame.images.load(name));
sprites[name] = sprite;
}
}
void loadGrid() {
tileSize = size.x / gameState.cols;
gridWidth = tileSize * gameState.cols;
gridHeight = tileSize * gameState.rows;
gridOffsetX = (size.x - gridWidth) / 2;
gridOffsetY = (size.y - gridHeight) / 2;
addGrid();
}
void addGrid() {
children
.whereType<SpriteComponent>()
.forEach((component) => component.removeFromParent());
for (var row = 0; row < gameState.rows; row++) {
for (var col = 0; col < gameState.cols; col++) {
final value = gameState.grid[row][col];
final spriteName = 'crystal$value.png';
final sprite = sprites[spriteName];
final spriteComponent = SpriteComponent()
..sprite = sprite
..width = tileSize
..height = tileSize
..x = gridOffsetX + col * tileSize
..y = gridOffsetY + row * tileSize;
add(spriteComponent);
}
}
}
@override
void render(Canvas canvas) {
super.render(canvas);
if (hasSelectedTile) {
canvas.drawRect(
Rect.fromLTWH(
selectedTile.x,
selectedTile.y,
tileSize,
tileSize,
),
selectedPaint,
);
}
}
@override
void onTapDown(TapDownInfo info) {
final x = info.eventPosition.global.x;
final y = info.eventPosition.global.y;
final col = ((x - gridOffsetX) / tileSize).floor();
final row = ((y - gridOffsetY) / tileSize).floor();
if (col >= 0 && col < gameState.cols && row >= 0 && row < gameState.rows) {
if (hasSelectedTile) {
gameState.selectElement(row, col);
hasSelectedTile = false;
addGrid();
} else {
selectedTile = SpriteComponent()
..x = gridOffsetX + col * tileSize
..y = gridOffsetY + row * tileSize
..width = tileSize
..height = tileSize;
hasSelectedTile = true;
}
}
}
}

View File

@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:match_magic/screen/game_screen.dart'; import 'package:match_magic/screen/game_screen.dart';
import 'package:match_magic/models/game_state.dart'; import 'package:match_magic/models/game_state.dart';
import 'package:provider/provider.dart';
void main() { void main() {
runApp( runApp(

View File

@ -1,5 +1,4 @@
import 'dart:math'; import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class GameState extends ChangeNotifier { class GameState extends ChangeNotifier {
@ -8,7 +7,6 @@ class GameState extends ChangeNotifier {
late List<List<int>> grid; late List<List<int>> grid;
int? selectedRow; int? selectedRow;
int? selectedCol; int? selectedCol;
bool isCheckingMatches = false;
GameState() { GameState() {
_initializeGrid(); _initializeGrid();
@ -21,7 +19,7 @@ class GameState extends ChangeNotifier {
} }
int _randomElement() { int _randomElement() {
return Random().nextInt(7) + 1; return Random().nextInt(6) + 1;
} }
void _removeInitialMatches() { void _removeInitialMatches() {
@ -48,7 +46,6 @@ class GameState extends ChangeNotifier {
swapElements(selectedRow!, selectedCol!, row, col); swapElements(selectedRow!, selectedCol!, row, col);
Future.delayed(const Duration(milliseconds: 300), () { Future.delayed(const Duration(milliseconds: 300), () {
if (!_checkMatches()) { if (!_checkMatches()) {
// Swap back if no match
swapElements(row, col, selectedRow!, selectedCol!); swapElements(row, col, selectedRow!, selectedCol!);
} }
selectedRow = null; selectedRow = null;
@ -92,7 +89,7 @@ class GameState extends ChangeNotifier {
_applyGravity(); _applyGravity();
Future.delayed(const Duration(milliseconds: 300), () { Future.delayed(const Duration(milliseconds: 300), () {
_fillEmptySpaces(); _fillEmptySpaces();
_checkMatches(); // Check again for new matches _checkMatches();
}); });
return true; return true;
} }
@ -103,54 +100,34 @@ class GameState extends ChangeNotifier {
final value = grid[row][col]; final value = grid[row][col];
if (value == 0) return false; if (value == 0) return false;
// Check horizontal match
int count = 1; int count = 1;
for (int i = col + 1; i < cols && grid[row][i] == value; i++) { for (int i = col + 1; i < cols && grid[row][i] == value; i++) count++;
count++; for (int i = col - 1; i >= 0 && grid[row][i] == value; i--) count++;
}
for (int i = col - 1; i >= 0 && grid[row][i] == value; i--) {
count++;
}
if (count >= 3) return true; if (count >= 3) return true;
// Check vertical match
count = 1; count = 1;
for (int i = row + 1; i < rows && grid[i][col] == value; i++) { for (int i = row + 1; i < rows && grid[i][col] == value; i++) count++;
count++; for (int i = row - 1; i >= 0 && grid[i][col] == value; i--) count++;
}
for (int i = row - 1; i >= 0 && grid[i][col] == value; i--) {
count++;
}
return count >= 3; return count >= 3;
} }
void _removeMatchedElements(int row, int col) { void _removeMatchedElements(int row, int col) {
final value = grid[row][col]; final value = grid[row][col];
// Remove horizontal matches
int left = col; int left = col;
while (left > 0 && grid[row][left - 1] == value) { while (left > 0 && grid[row][left - 1] == value) left--;
left--;
}
int right = col; int right = col;
while (right < cols - 1 && grid[row][right + 1] == value) { while (right < cols - 1 && grid[row][right + 1] == value) right++;
right++;
}
if (right - left + 1 >= 3) { if (right - left + 1 >= 3) {
for (int i = left; i <= right; i++) { for (int i = left; i <= right; i++) {
grid[row][i] = 0; grid[row][i] = 0;
} }
} }
// Remove vertical matches
int top = row; int top = row;
while (top > 0 && grid[top - 1][col] == value) { while (top > 0 && grid[top - 1][col] == value) top--;
top--;
}
int bottom = row; int bottom = row;
while (bottom < rows - 1 && grid[bottom + 1][col] == value) { while (bottom < rows - 1 && grid[bottom + 1][col] == value) bottom++;
bottom++;
}
if (bottom - top + 1 >= 3) { if (bottom - top + 1 >= 3) {
for (int i = top; i <= bottom; i++) { for (int i = top; i <= bottom; i++) {
grid[i][col] = 0; grid[i][col] = 0;
@ -178,9 +155,9 @@ class GameState extends ChangeNotifier {
for (int row = rows - 1; row >= 0; row--) { for (int row = rows - 1; row >= 0; row--) {
if (grid[row][col] == 0) { if (grid[row][col] == 0) {
grid[row][col] = _randomElement(); grid[row][col] = _randomElement();
}
}
}
notifyListeners(); notifyListeners();
} }
} }
}
}
}

View File

@ -1,5 +1,7 @@
import 'package:flame/game.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:match_magic/models/game_state.dart'; import 'package:match_magic/models/game_state.dart';
import 'package:match_magic/game/match_magic_game.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class GameScreen extends StatelessWidget { class GameScreen extends StatelessWidget {
@ -7,66 +9,11 @@ class GameScreen extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final gameState = context.watch<GameState>();
return Scaffold( return Scaffold(
appBar: AppBar( body: Consumer<GameState>(
title: const Text('Match Magic'), builder: (context, gameState, child) {
), return GameWidget<MatchMagicGame>(
body: LayoutBuilder( game: MatchMagicGame(gameState),
builder: (context, constraints) {
final tileSize = constraints.maxWidth / gameState.cols;
return Center(
child: Container(
width: constraints.maxWidth,
height: constraints.maxWidth,
child: Stack(
children: [
for (int row = 0; row < gameState.rows; row++)
for (int col = 0; col < gameState.cols; col++)
AnimatedPositioned(
key: ValueKey('${row}_$col${gameState.grid[row][col]}'),
duration: const Duration(milliseconds: 300),
left: col * tileSize,
top: row * tileSize,
child: GestureDetector(
onTap: () {
gameState.selectElement(row, col);
},
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
width: tileSize - 4,
height: tileSize - 4,
margin: const EdgeInsets.all(2.0),
decoration: BoxDecoration(
border: gameState.selectedRow == row &&
gameState.selectedCol == col
? Border.all(color: Colors.red, width: 2.0)
: null,
color: Colors.blue[100],
),
child: Center(
child: AnimatedOpacity(
opacity:
gameState.grid[row][col] != 0 ? 1.0 : 0.0,
duration: const Duration(milliseconds: 300),
child: gameState.grid[row][col] != 0
? Image.asset(
'assets/images/diamond${gameState.grid[row][col]}.png',
fit: BoxFit.cover,
width: tileSize - 4,
height: tileSize - 4,
)
: const SizedBox.shrink(),
),
),
),
),
),
],
),
),
); );
}, },
), ),

View File

@ -57,6 +57,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.1" version: "1.3.1"
flame:
dependency: "direct main"
description:
name: flame
sha256: "79133dc46a3ff870950f41d0dc1598414e7bd7ae2c29bd9f0a9de208d9a70cb7"
url: "https://pub.dev"
source: hosted
version: "1.18.0"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@ -139,6 +147,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.0" version: "1.0.0"
ordered_set:
dependency: transitive
description:
name: ordered_set
sha256: "1bfaaaee0419e43ecc9eaebd410eb4bd5039657b72011de75ff3e2915c9aac60"
url: "https://pub.dev"
source: hosted
version: "5.0.3"
path: path:
dependency: transitive dependency: transitive
description: description:
@ -226,4 +242,4 @@ packages:
version: "14.2.1" version: "14.2.1"
sdks: sdks:
dart: ">=3.4.3 <4.0.0" dart: ">=3.4.3 <4.0.0"
flutter: ">=3.18.0-18.0.pre.54" flutter: ">=3.22.0"

View File

@ -36,6 +36,7 @@ dependencies:
# The following adds the Cupertino Icons font to your application. # The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons. # Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.6 cupertino_icons: ^1.0.6
flame: ^1.18.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@ -61,13 +62,13 @@ flutter:
# To add assets to your application, add an assets section, like this: # To add assets to your application, add an assets section, like this:
assets: assets:
- assets/images/diamond1.png - assets/images/crystal1.png
- assets/images/diamond2.png - assets/images/crystal2.png
- assets/images/diamond3.png - assets/images/crystal3.png
- assets/images/diamond4.png - assets/images/crystal4.png
- assets/images/diamond5.png - assets/images/crystal5.png
- assets/images/diamond6.png - assets/images/crystal6.png
- assets/images/diamond7.png - assets/images/crystal0.png
# An image asset can refer to one or more resolution-specific "variants", see # An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware # https://flutter.dev/assets-and-images/#resolution-aware