ساخت بازی پازل با فلاتر – از صفر تا صد

۱۰۷۹
۱۴۰۲/۰۷/۱۵
۱۰ دقیقه
PDF
ساخت بازی پازل با فلاتر – از صفر تا صدساخت بازی پازل با فلاتر – از صفر تا صد
آموزش متنی جامع
امکان دانلود نسخه PDF

در این مقاله قصد داریم با روش ساخت بازی پازل با فلاتر آشنا شویم. این بازی روی دستگاه‌های مجهز به سیستم عامل iOS و همچنین اندروید کار خواهد کرد. اگر فلاتر را روی سیستم خود نصب نکرده‌اید، هم اینک به وب‌سایت فلاتر (+) بروید و آن را نصب کنید.

997696

تنظیم پروژه

زمانی که فلاتر را نصب کردید، باید یک پروژه جدید فلاتر در اندروید استودیو ایجاد کنید:

ساخت بازی پازل با فلاتردر صفحه بعدی گزینه Flutter Application را انتخاب کرده و نام پروژه جدید را به صورت flutter_puzzle تعیین کنید. برای تولید نام پکیج نیز می‌توانید از نام شرکت یا دامنه شخصی خودتان استفاده کنید. ما از عبارت dragosholban.com استفاده می‌کنیم و از این رو نام پکیج ما به صورت com.dragosholban.flutterpuzzle درمی‌آید. گنجاندن امکان پشتیبانی از سوئیفت یا کاتلین تغییری در فرایند کار نخواهد داشت، زیرا نیازی به نوشتن کدهای بومی اندروید یا iOS در این اپلیکیشن نخواهیم داشت.

پس از ایجاد شدن پروژه آن را روی شبیه‌ساز یا یک دستگاه واقعی اجرا کنید تا مطمئن شوید که همه چیز درست است.

ساخت بازی پازل با فلاتر

برای پایان تنظیم پروژه فایل lib/main.dart را باز کنید و محتوای آن را با کد زیر عوض کنید. بدین ترتیب کارکرد نمونه‌ای را که فلاتر در اپلیکیشن قرار داده است حذف کرده و آن را با نقطه آغازین اپلیکیشن خود جایگزین می‌کنیم.

1import 'package:flutter/material.dart';
2
3void main() => runApp(MyApp());
4
5class MyApp extends StatelessWidget {
6  @override
7  Widget build(BuildContext context) {
8    return MaterialApp(
9      title: 'Flutter Puzzle',
10      theme: ThemeData(
11        primarySwatch: Colors.blue,
12      ),
13      home: MyHomePage(title: 'Flutter Puzzle'),
14    );
15  }
16}
17
18class MyHomePage extends StatefulWidget {
19  final String title;
20
21  MyHomePage({Key key, this.title}) : super(key: key);
22
23  @override
24  _MyHomePageState createState() => _MyHomePageState();
25}
26
27class _MyHomePageState extends State<MyHomePage> {
28
29  @override
30  Widget build(BuildContext context) {
31    return Scaffold(
32      appBar: AppBar(
33        title: Text(widget.title),
34      ),
35      body: SafeArea(
36        child: new Center(
37          child: new Text('No image selected.'),
38        ),
39      ),
40      floatingActionButton: FloatingActionButton(
41        onPressed: () => null,
42        tooltip: 'New Image',
43        child: Icon(Icons.add),
44      ),
45    );
46  }
47}
مشاهده کامل کدها

چنان که مشاهده می‌کنید، این صرفاً یک صفحه خالی است. متن «No image selected» در میانه آن دیده می‌شود و یک دکمه شناور وجود دارد که در ادامه از آن استفاده خواهیم کرد:

ساخت بازی پازل با فلاتر

گرفتن تصویر از دوربین یا گالری

اکنون که همه چیز سر جای خود است، کار خود را این گونه آغاز می‌کنیم که به اپلیکیشن امکان می‌دهیم تصاویری را از حافظه داخلی گوشی بارگذاری کند یا مستقیماً عکس‌هایی به وسیله دوربین گوشی بگیرد. به این منظور از یک افزونه فلاتر به نام image_picker بهره می‌گیریم.

نصب افزونه‌های جدید در فلاتر به سادگی اضافه کردن آن‌ها به فایل pubspec.yaml است. سپس دستور flutter get را از کنسول یا مستقیماً از اندروید استودیو اجرا می‌کنیم:

1...
2dependencies:
3  flutter:
4    sdk: flutter
5
6  # The following adds the Cupertino Icons font to your application.
7  # Use with the CupertinoIcons class for iOS style icons.
8  cupertino_icons: ^0.1.2
9
10  image_picker: ^0.4.10
11...

از آنجا که قبلاً یک دکمه شناور اضافه کرده‌ایم، به وسیله آن به کاربران امکان می‌دهیم که یک تصویر جدید انتخاب کنند. به این منظور باید تغییراتی در کد لحاظ کنیم. ابتدا گزینه انتخاب منبع تصویر را ارائه می‌کنیم که به صورت دوربین یا گالری است. تغییرات زیر را در فایل main.dart اعمال کنید:

1// ...
2
3class _MyHomePageState extends State<MyHomePage> {
4 @override
5   Widget build(BuildContext context) {
6     return Scaffold(
7       appBar: AppBar(
8         title: Text(widget.title),
9       ),
10       body: SafeArea(
11         child: new Center(
12           child: new Text('No image selected.'),
13         ),
14       ),
15       floatingActionButton: FloatingActionButton(
16         onPressed: () {
17           showModalBottomSheet(context: context,
18               builder: (BuildContext context) {
19                 return SafeArea(
20                   child: new Column(
21                     mainAxisSize: MainAxisSize.min,
22                     children: [
23                       new ListTile(
24                         leading: new Icon(Icons.camera),
25                         title: new Text('Camera'),
26                         onTap: () => null,
27                       ),
28                       new ListTile(
29                         leading: new Icon(Icons.image),
30                         title: new Text('Gallery'),
31                         onTap: () => null,
32                       ),
33                     ],
34                   ),
35                 );
36               }
37           );
38         },
39         tooltip: 'New Image',
40         child: Icon(Icons.add),
41       ),
42     );
43   }
44 }
مشاهده کامل کدها

اکنون هنگامی که روی دکمه شناوری ضربه بزنید، منوی تحتانی ظاهر می‌شود و از شما می‌پرسد که منبع منتخب شما برای تصویر کدام است. مطمئن شوید که همه چیز به درستی کار می‌کند:

ساخت بازی پازل با فلاتر

برای دریافت عملی تصاویر، یک متد جدید ایجاد می‌کنیم که وقتی کاربر هر کدام از گزینه‌های دوربین یا گالری فوق را انتخاب می‌کند فراخوانی خواهد شد. زمانی که تصویر با تغییر حالت اپلیکیشن بارگذاری شد، آن را به کاربر نمایش می‌دهیم. همچنین توجه داشته باشید که باید برخی کتابخانه‌های dart و افزونه image_picker را که اضافه کرده‌ایم، ایمپورت کنیم:

1import 'dart:async';
2import 'dart:io';
3
4import 'package:flutter/material.dart';
5import 'package:image_picker/image_picker.dart';
6
7void main() => runApp(MyApp());
8
9// ...
10
11class _MyHomePageState extends State<MyHomePage> {
12  File _image;
13
14  Future getImage(ImageSource source) async {
15    var image = await ImagePicker.pickImage(source: source);
16
17    if (image != null) {
18      setState(() {
19        _image = image;
20      });
21    }
22  }
23
24  @override
25  Widget build(BuildContext context) {
26    return Scaffold(
27      appBar: AppBar(
28        title: Text(widget.title),
29      ),
30      body: SafeArea(
31        child: new Center(
32          child: _image == null
33            ? new Text('No image selected.')
34            : Image.file(_image)
35        ),
36      ),
37      floatingActionButton: FloatingActionButton(
38        onPressed: () {
39          showModalBottomSheet<void>(context: context,
40              builder: (BuildContext context) {
41                return SafeArea(
42                  child: new Column(
43                    mainAxisSize: MainAxisSize.min,
44                    children: <Widget>[
45                      new ListTile(
46                        leading: new Icon(Icons.camera),
47                        title: new Text('Camera'),
48                        onTap: () {
49                          getImage(ImageSource.camera);
50                          // this is how you dismiss the modal bottom sheet after making a choice
51                          Navigator.pop(context);
52                        },
53                      ),
54                      new ListTile(
55                        leading: new Icon(Icons.image),
56                        title: new Text('Gallery'),
57                        onTap: () {
58                          getImage(ImageSource.gallery);
59                          // dismiss the modal sheet
60                          Navigator.pop(context);
61                        },
62                      ),
63                    ],
64                  ),
65                );
66              }
67          );
68        },
69        tooltip: 'New Image',
70        child: Icon(Icons.add),
71      ),
72    );
73  }
74}
مشاهده کامل کدها

آخرین کاری که باید انجام دهیم، افزودن جزییات مجوزهای خاص iOS است. به این منظور فایل ios/Runner/Info.plist را باز کنید و دو خط دیگر مانند زیر اضافه کنید:

1<?xml version="1.0" encoding="UTF-8"?>
2<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3<plist version="1.0">
4<dict>
5    <key>CFBundleDevelopmentRegion</key>
6    <string>en</string>
7    ...
8    <key>NSPhotoLibraryUsageDescription</key>
9    <string>The app needs to access the Photo Library in order to be able to load images from it.</string>
10    <key>NSCameraUsageDescription</key>
11    <string>The app needs to access the Camera in order to be able to get images from it.</string>
12</dict>
13</plist>

اکنون می‌توانید اپلیکیشن را تست کنید. در این زمان باید بتوانید روی هر دو پلتفرم تصاویر را بارگذاری کرده یا با دوربین گوشی عکس‌هایی بگیرید. زمانی که کار بررسی به پایان رسید، به بخش بعدی بروید.

ساخت بازی پازل با فلاتر

تقسیم تصویر به تکه‌های پازل

ما برای ساخت تکه‌های پازل خود یک ویجت فلاتر به نام PuzzlePiece می‌سازیم. یک فایل جدید به نام PuzzlePiece.dart در پوشه lib ایجاد کنید.

این ویجت یک تصویر (image) می‌گیرد و آن را به مسیرهایی (path) برش می‌دهد که تکه‌های پازل را تشکیل می‌دهند. همچنین باید ردیف (row) و ستون (column) تکه‌ای که رسم می‌شود و بیشینه تعداد ردیف/ستون‌ها (maxRow و maxCol) را برای کل پازل بدانیم.

1import 'dart:math';
2
3import 'package:flutter/material.dart';
4
5class PuzzlePiece extends StatefulWidget {
6  final Image image;
7  final Size imageSize;
8  final int row;
9  final int col;
10  final int maxRow;
11  final int maxCol;
12
13  PuzzlePiece(
14      {Key key,
15        @required this.image,
16        @required this.imageSize,
17        @required this.row,
18        @required this.col,
19        @required this.maxRow,
20        @required this.maxCol})
21      : super(key: key);
22
23  @override
24  PuzzlePieceState createState() {
25    return new PuzzlePieceState();
26  }
27}
28
29class PuzzlePieceState extends State<PuzzlePiece> {
30  double top;
31  double left;
32
33  @override
34  Widget build(BuildContext context) {
35    final imageWidth = MediaQuery.of(context).size.width;
36    final imageHeight = MediaQuery.of(context).size.height * MediaQuery.of(context).size.width / widget.imageSize.width;
37    final pieceWidth = imageWidth / widget.maxCol;
38    final pieceHeight = imageHeight / widget.maxRow;
39
40    if (top == null) {
41      top = Random().nextInt((imageHeight - pieceHeight).ceil()).toDouble();
42      top -= widget.row * pieceHeight;
43    }
44    if (left == null) {
45      left = Random().nextInt((imageWidth - pieceWidth).ceil()).toDouble();
46      left -= widget.col * pieceWidth;
47    }
48
49    return Positioned(
50        top: top,
51        left: left,
52        width: imageWidth,
53        child: ClipPath(
54          child: CustomPaint(
55            foregroundPainter: PuzzlePiecePainter(widget.row, widget.col, widget.maxRow, widget.maxCol),
56            child: widget.image
57          ),
58        clipper: PuzzlePieceClipper(widget.row, widget.col, widget.maxRow, widget.maxCol),
59      ),
60    );
61  }
62}
63
64// this class is used to clip the image to the puzzle piece path
65class PuzzlePieceClipper extends CustomClipper<Path> {
66  final int row;
67  final int col;
68  final int maxRow;
69  final int maxCol;
70
71  PuzzlePieceClipper(this.row, this.col, this.maxRow, this.maxCol);
72
73  @override
74  Path getClip(Size size) {
75    return getPiecePath(size, row, col, maxRow, maxCol);
76  }
77
78  @override
79  bool shouldReclip(CustomClipper<Path> oldClipper) => false;
80}
81
82// this class is used to draw a border around the clipped image
83class PuzzlePiecePainter extends CustomPainter {
84  final int row;
85  final int col;
86  final int maxRow;
87  final int maxCol;
88
89  PuzzlePiecePainter(this.row, this.col, this.maxRow, this.maxCol);
90
91  @override
92  void paint(Canvas canvas, Size size) {
93    final Paint paint = Paint()
94      ..color = Color(0x80FFFFFF)
95      ..style = PaintingStyle.stroke
96      ..strokeWidth = 1.0;
97
98    canvas.drawPath(getPiecePath(size, row, col, maxRow, maxCol), paint);
99  }
100
101  @override
102  bool shouldRepaint(CustomPainter oldDelegate) {
103    return false;
104  }
105}
106
107// this is the path used to clip the image and, then, to draw a border around it; here we actually draw the puzzle piece
108Path getPiecePath(Size size, int row, int col, int maxRow, int maxCol) {
109  final width = size.width / maxCol;
110  final height = size.height / maxRow;
111  final offsetX = col * width;
112  final offsetY = row * height;
113  final bumpSize = height / 4;
114
115  var path = Path();
116  path.moveTo(offsetX, offsetY);
117
118  if (row == 0) {
119    // top side piece
120    path.lineTo(offsetX + width, offsetY);
121  } else {
122    // top bump
123    path.lineTo(offsetX + width / 3, offsetY);
124    path.cubicTo(offsetX + width / 6, offsetY - bumpSize, offsetX + width / 6 * 5, offsetY - bumpSize, offsetX + width / 3 * 2, offsetY);
125    path.lineTo(offsetX + width, offsetY);
126  }
127
128  if (col == maxCol - 1) {
129    // right side piece
130    path.lineTo(offsetX + width, offsetY + height);
131  } else {
132    // right bump
133    path.lineTo(offsetX + width, offsetY + height / 3);
134    path.cubicTo(offsetX + width - bumpSize, offsetY + height / 6, offsetX + width - bumpSize, offsetY + height / 6 * 5, offsetX + width, offsetY + height / 3 * 2);
135    path.lineTo(offsetX + width, offsetY + height);
136  }
137
138  if (row == maxRow - 1) {
139    // bottom side piece
140    path.lineTo(offsetX, offsetY + height);
141  } else {
142    // bottom bump
143    path.lineTo(offsetX + width / 3 * 2, offsetY + height);
144    path.cubicTo(offsetX + width / 6 * 5, offsetY + height - bumpSize, offsetX + width / 6, offsetY + height - bumpSize, offsetX + width / 3, offsetY + height);
145    path.lineTo(offsetX, offsetY + height);
146  }
147
148  if (col == 0) {
149    // left side piece
150    path.close();
151  } else {
152    // left bump
153    path.lineTo(offsetX, offsetY + height / 3 * 2);
154    path.cubicTo(offsetX - bumpSize, offsetY + height / 6 * 5, offsetX - bumpSize, offsetY + height / 6, offsetX, offsetY + height / 3);
155    path.close();
156  }
157
158  return path;
159}
مشاهده کامل کدها

اگر به فایل main.dart بازگردیم، کد زیر را به ابتدای کدهای موجود می‌افزاییم تا ویجت جدید فعال شود:

1import 'dart:async';
2import 'dart:io';
3
4import 'package:flutter/material.dart';
5import 'package:image_picker/image_picker.dart';
6
7import 'package:flutter_puzzle/PuzzlePiece.dart';
8
9void main() => runApp(MyApp());
10
11class MyApp extends StatelessWidget {
12  @override
13  Widget build(BuildContext context) {
14    return MaterialApp(
15      title: 'Flutter Puzzle',
16      theme: ThemeData(
17        primarySwatch: Colors.blue,
18      ),
19      home: MyHomePage(title: 'Flutter Puzzle'),
20    );
21  }
22}
23
24class MyHomePage extends StatefulWidget {
25  final String title;
26  final int rows = 3;
27  final int cols = 3;
28
29  MyHomePage({Key key, this.title}) : super(key: key);
30
31  @override
32  _MyHomePageState createState() => _MyHomePageState();
33}
34
35class _MyHomePageState extends State<MyHomePage> {
36  File _image;
37  List<Widget> pieces = [];
38
39  Future getImage(ImageSource source) async {
40    var image = await ImagePicker.pickImage(source: source);
41
42    if (image != null) {
43      setState(() {
44        _image = image;
45        pieces.clear();
46      });
47
48      splitImage(Image.file(image));
49    }
50  }
51  
52  // we need to find out the image size, to be used in the PuzzlePiece widget
53  Future<Size> getImageSize(Image image) async {
54    final Completer<Size> completer = Completer<Size>();
55
56    image.image.resolve(const ImageConfiguration()).addListener(
57          (ImageInfo info, bool _) {
58        completer.complete(Size(
59          info.image.width.toDouble(),
60          info.image.height.toDouble(),
61        ));
62      },
63    );
64
65    final Size imageSize = await completer.future;
66
67    return imageSize;
68  }
69
70  // here we will split the image into small pieces using the rows and columns defined above; each piece will be added to a stack
71  void splitImage(Image image) async {
72    Size imageSize = await getImageSize(image);
73
74    for (int x = 0; x < widget.rows; x++) {
75      for (int y = 0; y < widget.cols; y++) {
76        setState(() {
77          pieces.add(PuzzlePiece(key: GlobalKey(),
78              image: image,
79              imageSize: imageSize,
80              row: x,
81              col: y,
82              maxRow: widget.rows,
83              maxCol: widget.cols));
84        });
85      }
86    }
87  }
88
89  @override
90  Widget build(BuildContext context) {
91    return Scaffold(
92      appBar: AppBar(
93        title: Text(widget.title),
94      ),
95      body: SafeArea(
96        child: new Center(
97          child: _image == null
98            ? new Text('No image selected.')
99            : Stack(children: pieces)
100        ),
101      ),
102      floatingActionButton: FloatingActionButton(
103        onPressed: () {
104          showModalBottomSheet<void>(context: context,
105              builder: (BuildContext context) {
106                return SafeArea(
107                  child: new Column(
108                    mainAxisSize: MainAxisSize.min,
109                    children: <Widget>[
110                      new ListTile(
111                        leading: new Icon(Icons.camera),
112                        title: new Text('Camera'),
113                        onTap: () {
114                          getImage(ImageSource.camera);
115                          Navigator.pop(context);
116                        },
117                      ),
118                      new ListTile(
119                        leading: new Icon(Icons.image),
120                        title: new Text('Gallery'),
121                        onTap: () {
122                          getImage(ImageSource.gallery);
123                          Navigator.pop(context);
124                        },
125                      ),
126                    ],
127                  ),
128                );
129              }
130          );
131        },
132        tooltip: 'New Image',
133        child: Icon(Icons.add),
134      ),
135    );
136  }
137}
مشاهده کامل کدها

اینک اگر اپلیکیشن را اجرا و تصویر را بارگذاری کنید، می‌بینید که به چندین تکه پازل تبدیل می‌شود که به صورت تصادفی روی صفحه قرار گرفته‌اند.

ساخت بازی پازل با فلاتر

جابجایی تکه‌های پازل و چسباندن آن‌ها به هم

در این بخش یک «شناساگر ژست» (GestureDetector) فلاتر به پروژه خود اضافه می‌کنیم تا کاربر بتواند تکه‌های پازل را روی صفحه جابجا کند. وقتی که یک پازل به موقعیت نهایی خود نزدیک می‌شود، به آن می‌چسبد و دیگر نمی‌توان آن را جابجا کرد.

به این منظور کد هایلایت شده زیر ویجت PuzzlePiece را اضافه کنید:

1class PuzzlePiece extends StatefulWidget {
2  final Image image;
3  final Size imageSize;
4  final int row;
5  final int col;
6  final int maxRow;
7  final int maxCol;
8  final Function bringToTop;
9  final Function sendToBack;
10
11  PuzzlePiece(
12      {Key key,
13        @required this.image,
14        @required this.imageSize,
15        @required this.row,
16        @required this.col,
17        @required this.maxRow,
18        @required this.maxCol,
19        @required this.bringToTop,
20        @required this.sendToBack})
21      : super(key: key);
22
23  @override
24  PuzzlePieceState createState() {
25    return new PuzzlePieceState();
26  }
27}
28
29class PuzzlePieceState extends State<PuzzlePiece> {
30  double top;
31  double left;
32  bool isMovable = true;
33
34  @override
35  Widget build(BuildContext context) {
36    final imageWidth = MediaQuery.of(context).size.width;
37    final imageHeight = MediaQuery.of(context).size.height * MediaQuery.of(context).size.width / widget.imageSize.width;
38    final pieceWidth = imageWidth / widget.maxCol;
39    final pieceHeight = imageHeight / widget.maxRow;
40
41    if (top == null) {
42      top = Random().nextInt((imageHeight - pieceHeight).ceil()).toDouble();
43      top -= widget.row * pieceHeight;
44    }
45    if (left == null) {
46      left = Random().nextInt((imageWidth - pieceWidth).ceil()).toDouble();
47      left -= widget.col * pieceWidth;
48    }
49
50    return Positioned(
51        top: top,
52        left: left,
53        width: imageWidth,
54        child: GestureDetector(
55          onTap: () {
56            if (isMovable) {
57              widget.bringToTop(widget);
58            }
59          },
60          onPanStart: (_) {
61            if (isMovable) {
62              widget.bringToTop(widget);
63            }
64          },
65          onPanUpdate: (dragUpdateDetails) {
66            if (isMovable) {
67              setState(() {
68                top += dragUpdateDetails.delta.dy;
69                left += dragUpdateDetails.delta.dx;
70
71                if(-10 < top && top < 10 && -10 < left && left < 10) {
72                  top = 0;
73                  left = 0;
74                  isMovable = false;
75                  widget.sendToBack(widget);
76                }
77              });
78            }
79          },
80          child: ClipPath(
81            child: widget.image,
82            clipper: PuzzlePieceClipper(widget.row, widget.col, widget.maxRow, widget.maxCol),
83          ),
84        ),
85    );
86  }
87}
مشاهده کامل کدها

در نهایت کد زیر را به فایل main.dart اضافه کنید:

1void splitImage(Image image) async {
2  Size imageSize = await getImageSize(image);
3
4  for (int x = 0; x < widget.rows; x++) {
5    for (int y = 0; y < widget.cols; y++) {
6      setState(() {
7        pieces.add(PuzzlePiece(key: GlobalKey(),
8            image: image,
9            imageSize: imageSize,
10            row: x,
11            col: y,
12            maxRow: widget.rows,
13            maxCol: widget.cols,
14            bringToTop: this.bringToTop,
15            sendToBack: this.sendToBack));
16      });
17    }
18  }
19}
20
21// when the pan of a piece starts, we need to bring it to the front of the stack
22void bringToTop(Widget widget) {
23  setState(() {
24    pieces.remove(widget);
25    pieces.add(widget);
26  });
27}
28
29// when a piece reaches its final position, it will be sent to the back of the stack to not get in the way of other, still movable, pieces
30void sendToBack(Widget widget) {
31  setState(() {
32    pieces.remove(widget);
33    pieces.insert(0, widget);
34  });
35}
مشاهده کامل کدها

اپلیکیشن را یک بار دیگر اجرا کنید، تصویری را انتخاب کرده و تلاش کنید پازل را تکمیل کنید. اکنون همه چیز به خویی کار می‌کند و می‌توانید از اپلیکیشنی که ساخته‌اید بهره بگیرید.

اما کارهای دیگری نیز وجود دارد که می‌توانید برای بهبود بازی انجام دهید. یک نکته بدیهی آن است که وقتی همه تکه‌های پازل در جای خود قرار گرفتند، بازی را ری‌استارت کنید. نکته دیگر این است که بازی را به حالت پرتره محدود کنید. ما ترجیح می‌دهیم اجرای این وظایف را به عنوان تمرین بر عهده شما بگذاریم. توجه کنید که برای اجرای کار دوم باید اپلیکیشن را برای پلتفرم‌های iOS و Android به صورت مجزا پیکربندی کنید.

ساخت بازی پازل با فلاتر

امیدوارم از این راهنما در مورد ساخت بازی پازل با فلاتر استفاده برده باشید؛ برای مشاهده کد نهایی پروژه به این ریپوی گیت‌هاب (+) مراجعه کنید.

اگر این مطلب برای شما مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

==

بر اساس رای ۲ نفر
آیا این مطلب برای شما مفید بود؟
اگر پرسشی درباره این مطلب دارید، آن را با ما مطرح کنید.
منابع:
quick-code
PDF
مطالب مرتبط
۱ دیدگاه برای «ساخت بازی پازل با فلاتر – از صفر تا صد»

اسم برنامه چیه؟

نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *