1. शुरू करने से पहले
Flutter, डेवलपर को हॉट रिलोड और डिक्लेरेटिव यूज़र इंटरफ़ेस (यूआई) का इस्तेमाल करके, नए यूज़र इंटरफ़ेस को तुरंत बनाने में मदद करता है. हालांकि, एक समय ऐसा आता है, जब आपको इंटरफ़ेस में ज़्यादा इंटरैक्टिविटी जोड़ने की ज़रूरत होती है. ये टच, क्लिक करने पर बटन को ऐनिमेट करने जैसे आसान हो सकते हैं. साथ ही, ये जीपीयू की मदद से यूज़र इंटरफ़ेस को बदलने वाले शेडर जैसे मुश्किल भी हो सकते हैं.
इस कोडलैब में, आपको एक ऐसा Flutter ऐप्लिकेशन बनाना है जो एनिमेशन, शेडर, और पार्टिकल फ़ील्ड का इस्तेमाल करके, एक ऐसा यूज़र इंटरफ़ेस (यूआई) तैयार करता है जो उन साइंस फ़िक्शन फ़िल्मों और टीवी शो की याद दिलाता है जिन्हें हम कोडिंग के अलावा देखना पसंद करते हैं.
आपको क्या बनाना है
आपको पोस्ट-अपोकैलिप्टिक साइंस फ़िक्शन थीम वाले गेम के लिए शुरुआती मेन्यू पेज बनाना होगा. इसमें एक टाइटल है, जिसमें फ़्रैगमेंट शेडर का इस्तेमाल किया गया है. यह टेक्स्ट को विज़ुअली ऐनिमेट करने के लिए, टेक्स्ट का सैंपल लेता है. इसमें एक मुश्किल लेवल वाला मेन्यू है, जो पेज की कलर थीम को बदलता है. इसमें कई ऐनिमेशन भी हैं. साथ ही, इसमें एक ऐनिमेटेड गोला है, जिसे दूसरे फ़्रैगमेंट शेडर से पेंट किया गया है. अगर इससे भी काम नहीं बनता है, तो कोडलैब के आखिर में, पेज को दिलचस्प बनाने और उसमें मूवमेंट दिखाने के लिए, पार्टिकल इफ़ेक्ट जोड़ा जाएगा.
नीचे दिए गए स्क्रीनशॉट में, उन तीन डेस्कटॉप ऑपरेटिंग सिस्टम पर बनाए जाने वाले ऐप्लिकेशन दिखाए गए हैं जिन पर यह सुविधा काम करती है: Windows, Linux, और macOS. ज़्यादा जानकारी के लिए, वेब ब्राउज़र का वर्शन (यह भी काम करता है) दिया गया है. हर जगह ऐनिमेशन और फ़्रैगमेंट शेडर!
ज़रूरी शर्तें
- Dart की मदद से Flutter डेवलपमेंट की बुनियादी जानकारी. यह जानकारी, आपका पहला Flutter ऐप्लिकेशन कोडलैब में दी गई है
आपको क्या सीखने को मिलेगा
- बेहतर ऐनिमेशन बनाने के लिए,
flutter_animate
का इस्तेमाल कैसे करें - डेस्कटॉप और वेब पर, फ़्रैगमेंट शेडर के लिए Flutter की सहायता इस्तेमाल करने का तरीका
particle_field
की मदद से, अपने ऐप्लिकेशन में पार्टिकल ऐनिमेशन जोड़ने का तरीका
ज़रूरी शर्तें
- Flutter SDK
- Flutter और Dart के लिए VS Code सेटअप करना
- Windows, Linux या macOS के लिए, Flutter के डेस्कटॉप सपोर्ट को सेटअप करना
- Flutter के लिए वेब सपोर्ट सेटअप करना
2. अपनी प्रोफ़ाइल बनाना शुरू करें
स्टार्टर कोड डाउनलोड करना
- इस GitHub रिपॉज़िटरी पर जाएं.
- इस कोडलैब के सभी कोड डाउनलोड करने के लिए, कोड > ज़िप फ़ाइल डाउनलोड करें पर क्लिक करें.
codelabs-main
रूट फ़ोल्डर को अनपैक करने के लिए, डाउनलोड की गई ज़िप फ़ाइल को एक्स्ट्रैक्ट करें. आपको सिर्फ़next-gen-ui/
सबडायरेक्ट्री की ज़रूरत होगी. इसमेंstep_01
सेstep_06
फ़ोल्डर शामिल हैं. इनमें वह सोर्स कोड मौजूद है जिसका इस्तेमाल करके, आपको इस कोडलैब के हर चरण को पूरा करना है.
प्रोजेक्ट की डिपेंडेंसी डाउनलोड करना
- स्टार्टर प्रोजेक्ट खोलने के लिए, VS Code में File > Open folder > codelabs-main > next-gen-uis > step_01 पर क्लिक करें.
- अगर आपको VS Code में एक ऐसा डायलॉग दिखता है जिसमें आपको स्टार्टर ऐप्लिकेशन के लिए ज़रूरी पैकेज डाउनलोड करने के लिए कहा गया है, तो पैकेज पाएं पर क्लिक करें.
- अगर आपको VS Code में कोई ऐसा डायलॉग नहीं दिखता है जिसमें आपको स्टार्टर ऐप्लिकेशन के लिए ज़रूरी पैकेज डाउनलोड करने के लिए कहा गया हो, तो अपना टर्मिनल खोलें. इसके बाद,
step_01
फ़ोल्डर पर जाएं औरflutter pub get
कमांड चलाएं.
स्टार्टर ऐप्लिकेशन चलाना
- VS Code में, उस डेस्कटॉप ऑपरेटिंग सिस्टम को चुनें जिसका इस्तेमाल किया जा रहा है. अगर आपको वेब ब्राउज़र में अपने ऐप्लिकेशन की जांच करनी है, तो Chrome को चुनें.
उदाहरण के लिए, macOS को डिप्लॉयमेंट टारगेट के तौर पर इस्तेमाल करने पर, आपको यह दिखेगा:
Chrome को डिप्लॉयमेंट टारगेट के तौर पर इस्तेमाल करने पर, आपको यह दिखेगा:
lib/main.dart
फ़ाइल खोलें औरडीबग करना शुरू करें पर क्लिक करें. ऐप्लिकेशन, आपके डेस्कटॉप ऑपरेटिंग सिस्टम या Chrome ब्राउज़र में लॉन्च होता है.
स्टार्टर ऐप्लिकेशन के बारे में ज़्यादा जानें
स्टार्टर ऐप्लिकेशन में, इन बातों पर ध्यान दें:
- यूज़र इंटरफ़ेस (यूआई) बनाने के लिए तैयार है.
assets
डायरेक्ट्री में आर्ट ऐसेट और दो फ़्रैगमेंट शेडर हैं. इनका इस्तेमाल किया जाएगा.pubspec.yaml
फ़ाइल में, उन ऐसेट और पब पैकेज का कलेक्शन पहले से मौजूद होता है जिनका इस्तेमाल किया जाएगा.lib
डायरेक्ट्री में ये फ़ाइलें होती हैं:main.dart
फ़ाइल,assets.dart
फ़ाइल, औरstyles.dart
फ़ाइल.main.dart
फ़ाइल में, ज़रूरी जानकारी होती है.assets.dart
फ़ाइल में, आर्ट ऐसेट और फ़्रैगमेंट शेडर का पाथ होता है.styles.dart
फ़ाइल में, इस्तेमाल किए जाने वाले TextStyles और Colors की सूची होती है.lib
डायरेक्ट्री मेंcommon
डायरेक्ट्री भी होती है. इसमें कुछ काम की यूटिलिटी होती हैं, जिनका इस्तेमाल इस कोडलैब में किया जाएगा. साथ ही, इसमेंorb_shader
डायरेक्ट्री होती है. इसमें एकWidget
होता है, जिसका इस्तेमाल वर्टेक्स शेडर के साथ ऑर्ब दिखाने के लिए किया जाएगा.
ऐप्लिकेशन शुरू करने के बाद, आपको यह दिखेगा.
3. सीन को पेंट करना
इस चरण में, बैकग्राउंड आर्ट की सभी ऐसेट को स्क्रीन पर लेयर में रखा जाता है. शुरुआत में, यह इमेज मोनोक्रोम दिखेगी. हालांकि, इस चरण के आखिर में आपको सीन में रंग जोड़ने का विकल्प मिलेगा.
सीन में ऐसेट जोड़ना
- अपनी
lib
डायरेक्ट्री मेंtitle_screen
डायरेक्ट्री बनाएं. इसके बाद,title_screen.dart
फ़ाइल जोड़ें. फ़ाइल में यह कॉन्टेंट जोड़ें:
lib/title_screen/title_screen.dart
import 'package:flutter/material.dart';
import '../assets.dart';
class TitleScreen extends StatelessWidget {
const TitleScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Center(
child: Stack(
children: [
/// Bg-Base
Image.asset(AssetPaths.titleBgBase),
/// Bg-Receive
Image.asset(AssetPaths.titleBgReceive),
/// Mg-Base
Image.asset(AssetPaths.titleMgBase),
/// Mg-Receive
Image.asset(AssetPaths.titleMgReceive),
/// Mg-Emit
Image.asset(AssetPaths.titleMgEmit),
/// Fg-Rocks
Image.asset(AssetPaths.titleFgBase),
/// Fg-Receive
Image.asset(AssetPaths.titleFgReceive),
/// Fg-Emit
Image.asset(AssetPaths.titleFgEmit),
],
),
),
);
}
}
इस विजेट में, लेयर में स्टैक की गई ऐसेट वाला सीन शामिल है. बैकग्राउंड, मिडग्राउंड, और फ़ोरग्राउंड लेयर, हर एक को दो या तीन इमेज के ग्रुप से दिखाया जाता है. इन इमेज को अलग-अलग रंगों से रौशन किया जाएगा, ताकि यह पता चल सके कि सीन में रोशनी कैसे फैलती है.
main.dart
फ़ाइल में यह कॉन्टेंट जोड़ें:
lib/main.dart
import 'dart:io' show Platform;
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter/material.dart';
import 'package:window_size/window_size.dart';
// Remove 'styles.dart' import
import 'title_screen/title_screen.dart'; // Add this import
void main() {
if (!kIsWeb && (Platform.isWindows || Platform.isLinux || Platform.isMacOS)) {
WidgetsFlutterBinding.ensureInitialized();
setWindowMinSize(const Size(800, 500));
}
runApp(const NextGenApp());
}
class NextGenApp extends StatelessWidget {
const NextGenApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
themeMode: ThemeMode.dark,
darkTheme: ThemeData(brightness: Brightness.dark),
home: const TitleScreen(), // Replace with this widget
);
}
}
इससे ऐप्लिकेशन का यूज़र इंटरफ़ेस, आर्ट ऐसेट से बनाए गए मोनोक्रोम सीन से बदल जाता है. इसके बाद, हर लेयर को रंग दें.
इमेज में रंग भरने की सुविधा जोड़ना
title_screen.dart
फ़ाइल में यह कॉन्टेंट जोड़कर, इमेज में रंग भरने की सुविधा जोड़ें:
lib/title_screen/title_screen.dart
import 'package:flutter/material.dart';
import '../assets.dart';
class TitleScreen extends StatelessWidget {
const TitleScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Center(
child: Stack(
children: [
/// Bg-Base
Image.asset(AssetPaths.titleBgBase),
/// Bg-Receive
Image.asset(AssetPaths.titleBgReceive),
/// Mg-Base
Image.asset(AssetPaths.titleMgBase),
/// Mg-Receive
Image.asset(AssetPaths.titleMgReceive),
/// Mg-Emit
Image.asset(AssetPaths.titleMgEmit),
/// Fg-Rocks
Image.asset(AssetPaths.titleFgBase),
/// Fg-Receive
Image.asset(AssetPaths.titleFgReceive),
/// Fg-Emit
Image.asset(AssetPaths.titleFgEmit),
],
),
),
);
}
}
class _LitImage extends StatelessWidget { // Add from here...
const _LitImage({
required this.color,
required this.imgSrc,
required this.lightAmt,
});
final Color color;
final String imgSrc;
final double lightAmt;
@override
Widget build(BuildContext context) {
final hsl = HSLColor.fromColor(color);
return Image.asset(
imgSrc,
color: hsl.withLightness(hsl.lightness * lightAmt).toColor(),
colorBlendMode: BlendMode.modulate,
);
}
} // to here.
यह _LitImage
यूटिलिटी विजेट, हर आर्ट ऐसेट को फिर से रंग देता है. यह इस बात पर निर्भर करता है कि वे लाइट छोड़ रही हैं या लाइट पा रही हैं. ऐसा हो सकता है कि लिंटर से चेतावनी मिले, क्योंकि आपने अब तक इस नए विजेट का इस्तेमाल नहीं किया है.
रंगों में पेंट करना
title_screen.dart
फ़ाइल में बदलाव करके, रंग भरें. इसके लिए, यह तरीका अपनाएं:
lib/title_screen/title_screen.dart
import 'package:flutter/material.dart';
import '../assets.dart';
import '../styles.dart'; // Add this import
class TitleScreen extends StatelessWidget {
const TitleScreen({super.key});
final _finalReceiveLightAmt = 0.7; // Add this attribute
final _finalEmitLightAmt = 0.5; // And this attribute
@override
Widget build(BuildContext context) {
final orbColor = AppColors.orbColors[0]; // Add this final variable
final emitColor = AppColors.emitColors[0]; // And this one
return Scaffold(
backgroundColor: Colors.black,
body: Center(
child: Stack(
children: [
/// Bg-Base
Image.asset(AssetPaths.titleBgBase),
/// Bg-Receive
_LitImage( // Modify from here...
color: orbColor,
imgSrc: AssetPaths.titleBgReceive,
lightAmt: _finalReceiveLightAmt,
), // to here.
/// Mg-Base
_LitImage( // Modify from here...
imgSrc: AssetPaths.titleMgBase,
color: orbColor,
lightAmt: _finalReceiveLightAmt,
), // to here.
/// Mg-Receive
_LitImage( // Modify from here...
imgSrc: AssetPaths.titleMgReceive,
color: orbColor,
lightAmt: _finalReceiveLightAmt,
), // to here.
/// Mg-Emit
_LitImage( // Modify from here...
imgSrc: AssetPaths.titleMgEmit,
color: emitColor,
lightAmt: _finalEmitLightAmt,
), // to here.
/// Fg-Rocks
Image.asset(AssetPaths.titleFgBase),
/// Fg-Receive
_LitImage( // Modify from here...
imgSrc: AssetPaths.titleFgReceive,
color: orbColor,
lightAmt: _finalReceiveLightAmt,
), // to here.
/// Fg-Emit
_LitImage( // Modify from here...
imgSrc: AssetPaths.titleFgEmit,
color: emitColor,
lightAmt: _finalEmitLightAmt,
), // to here.
],
),
),
);
}
}
class _LitImage extends StatelessWidget {
const _LitImage({
required this.color,
required this.imgSrc,
required this.lightAmt,
});
final Color color;
final String imgSrc;
final double lightAmt;
@override
Widget build(BuildContext context) {
final hsl = HSLColor.fromColor(color);
return Image.asset(
imgSrc,
color: hsl.withLightness(hsl.lightness * lightAmt).toColor(),
colorBlendMode: BlendMode.modulate,
);
}
}
यहां ऐप्लिकेशन को फिर से दिखाया गया है. इस बार, आर्ट ऐसेट को हरे रंग में दिखाया गया है.
4. यूज़र इंटरफ़ेस (यूआई) जोड़ना
इस चरण में, पिछले चरण में बनाए गए सीन पर एक यूज़र इंटरफ़ेस रखा जाता है. इसमें टाइटल, मुश्किल लेवल चुनने वाले बटन, और सबसे ज़रूरी शुरू करें बटन शामिल है.
शीर्षक जोड़ें
lib/title_screen
डायरेक्ट्री मेंtitle_screen_ui.dart
फ़ाइल बनाएं और उसमें यह कॉन्टेंट जोड़ें:
lib/title_screen/title_screen_ui.dart
import 'package:extra_alignments/extra_alignments.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import '../assets.dart';
import '../common/ui_scaler.dart';
import '../styles.dart';
class TitleScreenUi extends StatelessWidget {
const TitleScreenUi({
super.key,
});
@override
Widget build(BuildContext context) {
return const Padding(
padding: EdgeInsets.symmetric(vertical: 40, horizontal: 50),
child: Stack(
children: [
/// Title Text
TopLeft(
child: UiScaler(
alignment: Alignment.topLeft,
child: _TitleText(),
),
),
],
),
);
}
}
class _TitleText extends StatelessWidget {
const _TitleText();
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Gap(20),
Row(
mainAxisSize: MainAxisSize.min,
children: [
Transform.translate(
offset: Offset(-(TextStyles.h1.letterSpacing! * .5), 0),
child: Text('OUTPOST', style: TextStyles.h1),
),
Image.asset(AssetPaths.titleSelectedLeft, height: 65),
Text('57', style: TextStyles.h2),
Image.asset(AssetPaths.titleSelectedRight, height: 65),
],
),
Text('INTO THE UNKNOWN', style: TextStyles.h3),
],
);
}
}
इस विजेट में, इस ऐप्लिकेशन का टाइटल और वे सभी बटन शामिल होते हैं जो इस ऐप्लिकेशन के यूज़र इंटरफ़ेस (यूआई) को बनाते हैं.
lib/title_screen/title_screen.dart
फ़ाइल को इस तरह अपडेट करें:
lib/title_screen/title_screen.dart
import 'package:flutter/material.dart';
import '../assets.dart';
import '../styles.dart';
import 'title_screen_ui.dart'; // Add this import
class TitleScreen extends StatelessWidget {
const TitleScreen({super.key});
final _finalReceiveLightAmt = 0.7;
final _finalEmitLightAmt = 0.5;
@override
Widget build(BuildContext context) {
final orbColor = AppColors.orbColors[0];
final emitColor = AppColors.emitColors[0];
return Scaffold(
backgroundColor: Colors.black,
body: Center(
child: Stack(
children: [
/// Bg-Base
Image.asset(AssetPaths.titleBgBase),
/// Bg-Receive
_LitImage(
color: orbColor,
imgSrc: AssetPaths.titleBgReceive,
lightAmt: _finalReceiveLightAmt,
),
/// Mg-Base
_LitImage(
imgSrc: AssetPaths.titleMgBase,
color: orbColor,
lightAmt: _finalReceiveLightAmt,
),
/// Mg-Receive
_LitImage(
imgSrc: AssetPaths.titleMgReceive,
color: orbColor,
lightAmt: _finalReceiveLightAmt,
),
/// Mg-Emit
_LitImage(
imgSrc: AssetPaths.titleMgEmit,
color: emitColor,
lightAmt: _finalEmitLightAmt,
),
/// Fg-Rocks
Image.asset(AssetPaths.titleFgBase),
/// Fg-Receive
_LitImage(
imgSrc: AssetPaths.titleFgReceive,
color: orbColor,
lightAmt: _finalReceiveLightAmt,
),
/// Fg-Emit
_LitImage(
imgSrc: AssetPaths.titleFgEmit,
color: emitColor,
lightAmt: _finalEmitLightAmt,
),
/// UI
const Positioned.fill( // Add from here...
child: TitleScreenUi(),
), // to here.
],
),
),
);
}
}
class _LitImage extends StatelessWidget {
const _LitImage({
required this.color,
required this.imgSrc,
required this.lightAmt,
});
final Color color;
final String imgSrc;
final double lightAmt;
@override
Widget build(BuildContext context) {
final hsl = HSLColor.fromColor(color);
return Image.asset(
imgSrc,
color: hsl.withLightness(hsl.lightness * lightAmt).toColor(),
colorBlendMode: BlendMode.modulate,
);
}
}
इस कोड को चलाने पर टाइटल दिखता है, जो यूज़र इंटरफ़ेस की शुरुआत होती है.
कठिनाई के लेवल वाले बटन जोड़ना
focusable_control_builder
पैकेज के लिए नया इंपोर्ट जोड़कर,title_screen_ui.dart
को अपडेट करें:
lib/title_screen/title_screen_ui.dart
import 'package:extra_alignments/extra_alignments.dart';
import 'package:flutter/material.dart';
import 'package:focusable_control_builder/focusable_control_builder.dart'; // Add import
import 'package:gap/gap.dart';
import '../assets.dart';
import '../common/ui_scaler.dart';
import '../styles.dart';
TitleScreenUi
विजेट में यह जानकारी जोड़ें:
lib/title_screen/title_screen_ui.dart
class TitleScreenUi extends StatelessWidget {
const TitleScreenUi({
super.key,
required this.difficulty, // Edit from here...
required this.onDifficultyPressed,
required this.onDifficultyFocused,
});
final int difficulty;
final void Function(int difficulty) onDifficultyPressed;
final void Function(int? difficulty) onDifficultyFocused; // to here.
@override
Widget build(BuildContext context) {
return Padding( // Move this const...
padding: const EdgeInsets.symmetric(vertical: 40, horizontal: 50), // to here.
child: Stack(
children: [
/// Title Text
const TopLeft( // Add a const here, as well
child: UiScaler(
alignment: Alignment.topLeft,
child: _TitleText(),
),
),
/// Difficulty Btns
BottomLeft( // Add from here...
child: UiScaler(
alignment: Alignment.bottomLeft,
child: _DifficultyBtns(
difficulty: difficulty,
onDifficultyPressed: onDifficultyPressed,
onDifficultyFocused: onDifficultyFocused,
),
),
), // to here.
],
),
);
}
}
- कठिनाई के लेवल वाले बटन लागू करने के लिए, यहां दिए गए दो विजेट जोड़ें:
lib/title_screen/title_screen_ui.dart
class _DifficultyBtns extends StatelessWidget {
const _DifficultyBtns({
required this.difficulty,
required this.onDifficultyPressed,
required this.onDifficultyFocused,
});
final int difficulty;
final void Function(int difficulty) onDifficultyPressed;
final void Function(int? difficulty) onDifficultyFocused;
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
_DifficultyBtn(
label: 'Casual',
selected: difficulty == 0,
onPressed: () => onDifficultyPressed(0),
onHover: (over) => onDifficultyFocused(over ? 0 : null),
),
_DifficultyBtn(
label: 'Normal',
selected: difficulty == 1,
onPressed: () => onDifficultyPressed(1),
onHover: (over) => onDifficultyFocused(over ? 1 : null),
),
_DifficultyBtn(
label: 'Hardcore',
selected: difficulty == 2,
onPressed: () => onDifficultyPressed(2),
onHover: (over) => onDifficultyFocused(over ? 2 : null),
),
const Gap(20),
],
);
}
}
class _DifficultyBtn extends StatelessWidget {
const _DifficultyBtn({
required this.selected,
required this.onPressed,
required this.onHover,
required this.label,
});
final String label;
final bool selected;
final VoidCallback onPressed;
final void Function(bool hasFocus) onHover;
@override
Widget build(BuildContext context) {
return FocusableControlBuilder(
onPressed: onPressed,
onHoverChanged: (_, state) => onHover.call(state.isHovered),
builder: (_, state) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: SizedBox(
width: 250,
height: 60,
child: Stack(
children: [
/// Bg with fill and outline
Container(
decoration: BoxDecoration(
color: const Color(0xFF00D1FF).withOpacity(.1),
border: Border.all(color: Colors.white, width: 5),
),
),
if (state.isHovered || state.isFocused) ...[
Container(
decoration: BoxDecoration(
color: const Color(0xFF00D1FF).withOpacity(.1),
),
),
],
/// cross-hairs (selected state)
if (selected) ...[
CenterLeft(
child: Image.asset(AssetPaths.titleSelectedLeft),
),
CenterRight(
child: Image.asset(AssetPaths.titleSelectedRight),
),
],
/// Label
Center(
child: Text(label.toUpperCase(), style: TextStyles.btn),
),
],
),
),
);
},
);
}
}
TitleScreen
विजेट को स्टेटलेस से स्टेटफ़ुल में बदलें. साथ ही, कठिनाई के आधार पर कलर स्कीम बदलने की सुविधा चालू करने के लिए, स्टेट जोड़ें:
lib/title_screen/title_screen.dart
import 'package:flutter/material.dart';
import '../assets.dart';
import '../styles.dart';
import 'title_screen_ui.dart';
class TitleScreen extends StatefulWidget {
const TitleScreen({super.key});
@override
State<TitleScreen> createState() => _TitleScreenState();
}
class _TitleScreenState extends State<TitleScreen> {
Color get _emitColor =>
AppColors.emitColors[_difficultyOverride ?? _difficulty];
Color get _orbColor =>
AppColors.orbColors[_difficultyOverride ?? _difficulty];
/// Currently selected difficulty
int _difficulty = 0;
/// Currently focused difficulty (if any)
int? _difficultyOverride;
void _handleDifficultyPressed(int value) {
setState(() => _difficulty = value);
}
void _handleDifficultyFocused(int? value) {
setState(() => _difficultyOverride = value);
}
final _finalReceiveLightAmt = 0.7;
final _finalEmitLightAmt = 0.5;
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Center(
child: Stack(
children: [
/// Bg-Base
Image.asset(AssetPaths.titleBgBase),
/// Bg-Receive
_LitImage(
color: _orbColor,
imgSrc: AssetPaths.titleBgReceive,
lightAmt: _finalReceiveLightAmt,
),
/// Mg-Base
_LitImage(
imgSrc: AssetPaths.titleMgBase,
color: _orbColor,
lightAmt: _finalReceiveLightAmt,
),
/// Mg-Receive
_LitImage(
imgSrc: AssetPaths.titleMgReceive,
color: _orbColor,
lightAmt: _finalReceiveLightAmt,
),
/// Mg-Emit
_LitImage(
imgSrc: AssetPaths.titleMgEmit,
color: _emitColor,
lightAmt: _finalEmitLightAmt,
),
/// Fg-Rocks
Image.asset(AssetPaths.titleFgBase),
/// Fg-Receive
_LitImage(
imgSrc: AssetPaths.titleFgReceive,
color: _orbColor,
lightAmt: _finalReceiveLightAmt,
),
/// Fg-Emit
_LitImage(
imgSrc: AssetPaths.titleFgEmit,
color: _emitColor,
lightAmt: _finalEmitLightAmt,
),
/// UI
Positioned.fill(
child: TitleScreenUi(
difficulty: _difficulty,
onDifficultyFocused: _handleDifficultyFocused,
onDifficultyPressed: _handleDifficultyPressed,
),
),
],
),
),
);
}
}
class _LitImage extends StatelessWidget {
const _LitImage({
required this.color,
required this.imgSrc,
required this.lightAmt,
});
final Color color;
final String imgSrc;
final double lightAmt;
@override
Widget build(BuildContext context) {
final hsl = HSLColor.fromColor(color);
return Image.asset(
imgSrc,
color: hsl.withLightness(hsl.lightness * lightAmt).toColor(),
colorBlendMode: BlendMode.modulate,
);
}
}
यहां दो अलग-अलग मुश्किल लेवल की सेटिंग के लिए यूज़र इंटरफ़ेस (यूआई) दिखाया गया है. ध्यान दें कि ग्रेस्केल इमेज पर मास्क के तौर पर इस्तेमाल किए गए मुश्किल लेवल के रंगों से, असल लगने वाला रिफ़्लेक्टिव इफ़ेक्ट मिलता है!
'शुरू करें' बटन जोड़ना
title_screen_ui.dart
फ़ाइल अपडेट करें.TitleScreenUi
विजेट में यह जानकारी जोड़ें:
lib/title_screen/title_screen_ui.dart
class TitleScreenUi extends StatelessWidget {
const TitleScreenUi({
super.key,
required this.difficulty,
required this.onDifficultyPressed,
required this.onDifficultyFocused,
});
final int difficulty;
final void Function(int difficulty) onDifficultyPressed;
final void Function(int? difficulty) onDifficultyFocused;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 40, horizontal: 50),
child: Stack(
children: [
/// Title Text
const TopLeft(
child: UiScaler(
alignment: Alignment.topLeft,
child: _TitleText(),
),
),
/// Difficulty Btns
BottomLeft(
child: UiScaler(
alignment: Alignment.bottomLeft,
child: _DifficultyBtns(
difficulty: difficulty,
onDifficultyPressed: onDifficultyPressed,
onDifficultyFocused: onDifficultyFocused,
),
),
),
/// StartBtn
BottomRight( // Add from here...
child: UiScaler(
alignment: Alignment.bottomRight,
child: Padding(
padding: const EdgeInsets.only(bottom: 20, right: 40),
child: _StartBtn(onPressed: () {}),
),
),
), // to here.
],
),
);
}
}
- स्टार्ट बटन को लागू करने के लिए, यह विजेट जोड़ें:
lib/title_screen/title_screen_ui.dart
class _StartBtn extends StatefulWidget {
const _StartBtn({required this.onPressed});
final VoidCallback onPressed;
@override
State<_StartBtn> createState() => _StartBtnState();
}
class _StartBtnState extends State<_StartBtn> {
AnimationController? _btnAnim;
bool _wasHovered = false;
@override
Widget build(BuildContext context) {
return FocusableControlBuilder(
cursor: SystemMouseCursors.click,
onPressed: widget.onPressed,
builder: (_, state) {
if ((state.isHovered || state.isFocused) &&
!_wasHovered &&
_btnAnim?.status != AnimationStatus.forward) {
_btnAnim?.forward(from: 0);
}
_wasHovered = (state.isHovered || state.isFocused);
return SizedBox(
width: 520,
height: 100,
child: Stack(
children: [
Positioned.fill(child: Image.asset(AssetPaths.titleStartBtn)),
if (state.isHovered || state.isFocused) ...[
Positioned.fill(
child: Image.asset(AssetPaths.titleStartBtnHover)),
],
Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text('START MISSION',
style: TextStyles.btn
.copyWith(fontSize: 24, letterSpacing: 18)),
],
),
),
],
),
);
},
);
}
}
यहां ऐप्लिकेशन को बटन के पूरे कलेक्शन के साथ दिखाया गया है.
5. ऐनिमेशन जोड़ना
इस चरण में, आर्ट ऐसेट के लिए यूज़र इंटरफ़ेस और रंग के ट्रांज़िशन को ऐनिमेट किया जाता है.
टाइटल को फ़ेड इन करें
इस चरण में, Flutter ऐप्लिकेशन को ऐनिमेट करने के लिए कई तरीकों का इस्तेमाल किया जाता है. इनमें से एक तरीका, flutter_animate
का इस्तेमाल करना है. इस पैकेज की मदद से बनाए गए ऐनिमेशन, ऐप्लिकेशन को हॉट रिलोड करने पर अपने-आप फिर से चल सकते हैं. इससे डेवलपमेंट के काम को तेज़ी से पूरा किया जा सकता है.
lib/main.dart
में मौजूद कोड में इस तरह बदलाव करें:
lib/main.dart
import 'dart:io' show Platform;
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart'; // Add this import
import 'package:window_size/window_size.dart';
import 'title_screen/title_screen.dart';
void main() {
if (!kIsWeb && (Platform.isWindows || Platform.isLinux || Platform.isMacOS)) {
WidgetsFlutterBinding.ensureInitialized();
setWindowMinSize(const Size(800, 500));
}
Animate.restartOnHotReload = true; // Add this line
runApp(const NextGenApp());
}
class NextGenApp extends StatelessWidget {
const NextGenApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
themeMode: ThemeMode.dark,
darkTheme: ThemeData(brightness: Brightness.dark),
home: const TitleScreen(),
);
}
}
flutter_animate
पैकेज का फ़ायदा पाने के लिए, आपको इसे इंपोर्ट करना होगा.lib/title_screen/title_screen_ui.dart
में इंपोर्ट जोड़ें. इसके लिए, यह तरीका अपनाएं:
lib/title_screen/title_screen_ui.dart
import 'package:extra_alignments/extra_alignments.dart';
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart'; // Add this import
import 'package:focusable_control_builder/focusable_control_builder.dart';
import 'package:gap/gap.dart';
import '../assets.dart';
import '../common/ui_scaler.dart';
import '../styles.dart';
class TitleScreenUi extends StatelessWidget {
_TitleText
विजेट में बदलाव करके, टाइटल में ऐनिमेशन जोड़ें. इसके लिए, यह तरीका अपनाएं:
lib/title_screen/title_screen_ui.dart
class _TitleText extends StatelessWidget {
const _TitleText();
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Gap(20),
Row(
mainAxisSize: MainAxisSize.min,
children: [
Transform.translate(
offset: Offset(-(TextStyles.h1.letterSpacing! * .5), 0),
child: Text('OUTPOST', style: TextStyles.h1),
),
Image.asset(AssetPaths.titleSelectedLeft, height: 65),
Text('57', style: TextStyles.h2),
Image.asset(AssetPaths.titleSelectedRight, height: 65),
], // Edit from here...
).animate().fadeIn(delay: .8.seconds, duration: .7.seconds),
Text('INTO THE UNKNOWN', style: TextStyles.h3)
.animate()
.fadeIn(delay: 1.seconds, duration: .7.seconds),
], // to here.
);
}
}
- टाइटल को फ़ेड इन होते हुए देखने के लिए, फिर से लोड करें दबाएं.
मुश्किल लेवल वाले बटन को धीरे-धीरे दिखाना
_DifficultyBtns
विजेट में बदलाव करके, मुश्किल लेवल के बटन के दिखने के समय ऐनिमेशन जोड़ें. इसके लिए, यह तरीका अपनाएं:
lib/title_screen/title_screen_ui.dart
class _DifficultyBtns extends StatelessWidget {
const _DifficultyBtns({
required this.difficulty,
required this.onDifficultyPressed,
required this.onDifficultyFocused,
});
final int difficulty;
final void Function(int difficulty) onDifficultyPressed;
final void Function(int? difficulty) onDifficultyFocused;
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
_DifficultyBtn(
label: 'Casual',
selected: difficulty == 0,
onPressed: () => onDifficultyPressed(0),
onHover: (over) => onDifficultyFocused(over ? 0 : null),
) // Add from here...
.animate()
.fadeIn(delay: 1.3.seconds, duration: .35.seconds)
.slide(begin: const Offset(0, .2)), // to here
_DifficultyBtn(
label: 'Normal',
selected: difficulty == 1,
onPressed: () => onDifficultyPressed(1),
onHover: (over) => onDifficultyFocused(over ? 1 : null),
) // Add from here...
.animate()
.fadeIn(delay: 1.5.seconds, duration: .35.seconds)
.slide(begin: const Offset(0, .2)), // to here
_DifficultyBtn(
label: 'Hardcore',
selected: difficulty == 2,
onPressed: () => onDifficultyPressed(2),
onHover: (over) => onDifficultyFocused(over ? 2 : null),
) // Add from here...
.animate()
.fadeIn(delay: 1.7.seconds, duration: .35.seconds)
.slide(begin: const Offset(0, .2)), // to here
const Gap(20),
],
);
}
}
- मुश्किल सवालों के बटन को क्रम से दिखाने के लिए, फिर से लोड करें दबाएं. साथ ही, बोनस के तौर पर बटन को थोड़ा ऊपर की ओर स्लाइड होते हुए देखें.
'शुरू करें' बटन को फ़ेड इन करें
_StartBtnState
स्टेट क्लास में बदलाव करके, स्टार्ट बटन में ऐनिमेशन जोड़ें. इसके लिए, यह तरीका अपनाएं:
lib/title_screen/title_screen_ui.dart
class _StartBtnState extends State<_StartBtn> {
AnimationController? _btnAnim;
bool _wasHovered = false;
@override
Widget build(BuildContext context) {
return FocusableControlBuilder(
cursor: SystemMouseCursors.click,
onPressed: widget.onPressed,
builder: (_, state) {
if ((state.isHovered || state.isFocused) &&
!_wasHovered &&
_btnAnim?.status != AnimationStatus.forward) {
_btnAnim?.forward(from: 0);
}
_wasHovered = (state.isHovered || state.isFocused);
return SizedBox(
width: 520,
height: 100,
child: Stack(
children: [
Positioned.fill(child: Image.asset(AssetPaths.titleStartBtn)),
if (state.isHovered || state.isFocused) ...[
Positioned.fill(
child: Image.asset(AssetPaths.titleStartBtnHover)),
],
Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text('START MISSION',
style: TextStyles.btn
.copyWith(fontSize: 24, letterSpacing: 18)),
],
),
),
],
) // Edit from here...
.animate(autoPlay: false, onInit: (c) => _btnAnim = c)
.shimmer(duration: .7.seconds, color: Colors.black),
)
.animate()
.fadeIn(delay: 2.3.seconds)
.slide(begin: const Offset(0, .2));
}, // to here.
);
}
}
- मुश्किल सवालों के बटन को क्रम से दिखाने के लिए, फिर से लोड करें दबाएं. साथ ही, बोनस के तौर पर बटन को थोड़ा ऊपर की ओर स्लाइड होते हुए देखें.
कठिनाई के लेवल पर कर्सर घुमाने पर दिखने वाले इफ़ेक्ट को ऐनिमेट करना
_DifficultyBtn
स्टेट क्लास में बदलाव करके, मुश्किल लेवल वाले बटन की होवर स्टेट में ऐनिमेशन जोड़ें. इसके लिए, यह तरीका अपनाएं:
lib/title_screen/title_screen_ui.dart
class _DifficultyBtn extends StatelessWidget {
const _DifficultyBtn({
required this.selected,
required this.onPressed,
required this.onHover,
required this.label,
});
final String label;
final bool selected;
final VoidCallback onPressed;
final void Function(bool hasFocus) onHover;
@override
Widget build(BuildContext context) {
return FocusableControlBuilder(
onPressed: onPressed,
onHoverChanged: (_, state) => onHover.call(state.isHovered),
builder: (_, state) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: SizedBox(
width: 250,
height: 60,
child: Stack(
children: [
/// Bg with fill and outline
AnimatedOpacity( // Edit from here
opacity: (!selected && (state.isHovered || state.isFocused))
? 1
: 0,
duration: .3.seconds,
child: Container(
decoration: BoxDecoration(
color: const Color(0xFF00D1FF).withOpacity(.1),
border: Border.all(color: Colors.white, width: 5),
),
),
), // to here.
if (state.isHovered || state.isFocused) ...[
Container(
decoration: BoxDecoration(
color: const Color(0xFF00D1FF).withOpacity(.1),
),
),
],
/// cross-hairs (selected state)
if (selected) ...[
CenterLeft(
child: Image.asset(AssetPaths.titleSelectedLeft),
),
CenterRight(
child: Image.asset(AssetPaths.titleSelectedRight),
),
],
/// Label
Center(
child: Text(label.toUpperCase(), style: TextStyles.btn),
),
],
),
),
);
},
);
}
}
अब मुश्किल लेवल वाले बटन पर, कर्सर घुमाने पर BoxDecoration
दिखता है. ऐसा तब होता है, जब किसी बटन को नहीं चुना गया हो.
रंग बदलने की प्रोसेस को ऐनिमेट करना
- बैकग्राउंड का रंग तुरंत और अचानक बदल जाता है. बेहतर होगा कि कलर स्कीम के बीच, रोशनी वाली इमेज को ऐनिमेट किया जाए.
lib/title_screen/title_screen.dart
मेंflutter_animate
जोड़ें:
lib/title_screen/title_screen.dart
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart'; // Add this import
import '../assets.dart';
import '../styles.dart';
import 'title_screen_ui.dart';
class TitleScreen extends StatefulWidget {
lib/title_screen/title_screen.dart
में_AnimatedColors
विजेट जोड़ने के लिए:
lib/title_screen/title_screen.dart
class _AnimatedColors extends StatelessWidget {
const _AnimatedColors({
required this.emitColor,
required this.orbColor,
required this.builder,
});
final Color emitColor;
final Color orbColor;
final Widget Function(BuildContext context, Color orbColor, Color emitColor)
builder;
@override
Widget build(BuildContext context) {
final duration = .5.seconds;
return TweenAnimationBuilder(
tween: ColorTween(begin: emitColor, end: emitColor),
duration: duration,
builder: (_, emitColor, __) {
return TweenAnimationBuilder(
tween: ColorTween(begin: orbColor, end: orbColor),
duration: duration,
builder: (context, orbColor, __) {
return builder(context, orbColor!, emitColor!);
},
);
},
);
}
}
- अभी बनाए गए विजेट का इस्तेमाल करके, लाइट वाली इमेज के रंगों को ऐनिमेट करें. इसके लिए,
_TitleScreenState
मेंbuild
मैथड को इस तरह अपडेट करें:
lib/title_screen/title_screen.dart
class _TitleScreenState extends State<TitleScreen> {
Color get _emitColor =>
AppColors.emitColors[_difficultyOverride ?? _difficulty];
Color get _orbColor =>
AppColors.orbColors[_difficultyOverride ?? _difficulty];
/// Selected difficulty
int _difficulty = 0;
/// Focused difficulty (if any)
int? _difficultyOverride;
void _handleDifficultyPressed(int value) {
setState(() => _difficulty = value);
}
void _handleDifficultyFocused(int? value) {
setState(() => _difficultyOverride = value);
}
final _finalReceiveLightAmt = 0.7;
final _finalEmitLightAmt = 0.5;
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Center(
child: _AnimatedColors( // Edit from here...
orbColor: _orbColor,
emitColor: _emitColor,
builder: (_, orbColor, emitColor) {
return Stack(
children: [
/// Bg-Base
Image.asset(AssetPaths.titleBgBase),
/// Bg-Receive
_LitImage(
color: orbColor,
imgSrc: AssetPaths.titleBgReceive,
lightAmt: _finalReceiveLightAmt,
),
/// Mg-Base
_LitImage(
imgSrc: AssetPaths.titleMgBase,
color: orbColor,
lightAmt: _finalReceiveLightAmt,
),
/// Mg-Receive
_LitImage(
imgSrc: AssetPaths.titleMgReceive,
color: orbColor,
lightAmt: _finalReceiveLightAmt,
),
/// Mg-Emit
_LitImage(
imgSrc: AssetPaths.titleMgEmit,
color: emitColor,
lightAmt: _finalEmitLightAmt,
),
/// Fg-Rocks
Image.asset(AssetPaths.titleFgBase),
/// Fg-Receive
_LitImage(
imgSrc: AssetPaths.titleFgReceive,
color: orbColor,
lightAmt: _finalReceiveLightAmt,
),
/// Fg-Emit
_LitImage(
imgSrc: AssetPaths.titleFgEmit,
color: emitColor,
lightAmt: _finalEmitLightAmt,
),
/// UI
Positioned.fill(
child: TitleScreenUi(
difficulty: _difficulty,
onDifficultyFocused: _handleDifficultyFocused,
onDifficultyPressed: _handleDifficultyPressed,
),
),
],
).animate().fadeIn(duration: 1.seconds, delay: .3.seconds);
},
), // to here.
),
);
}
}
इस आखिरी बदलाव के साथ, आपने स्क्रीन पर मौजूद हर एलिमेंट में ऐनिमेशन जोड़ दिए हैं. अब यह पहले से ज़्यादा बेहतर लग रहा है!
6. फ़्रैगमेंट शेडर जोड़ना
इस चरण में, ऐप्लिकेशन में फ़्रैगमेंट शेडर जोड़े जाते हैं. सबसे पहले, टाइटल में बदलाव करने के लिए शेडर का इस्तेमाल किया जाता है, ताकि इसे और ज़्यादा डरावना बनाया जा सके. इसके बाद, पेज के बीच में एक गोला बनाने के लिए, दूसरा शेडर जोड़ा जाता है.
फ़्रैगमेंट शेडर की मदद से टाइटल को बिगाड़ना
इस बदलाव के साथ, provider
पैकेज जोड़ा गया है. इससे कंपाइल किए गए शेडर को विजेट ट्री में पास किया जा सकता है. अगर आपको यह जानना है कि शेडर कैसे लोड होते हैं, तो lib/assets.dart
में लागू करने का तरीका देखें.
lib/main.dart
में मौजूद कोड में इस तरह बदलाव करें:
lib/main.dart
import 'dart:io' show Platform;
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:provider/provider.dart'; // Add this import
import 'package:window_size/window_size.dart';
import 'assets.dart'; // Add this import
import 'title_screen/title_screen.dart';
void main() {
if (!kIsWeb && (Platform.isWindows || Platform.isLinux || Platform.isMacOS)) {
WidgetsFlutterBinding.ensureInitialized();
setWindowMinSize(const Size(800, 500));
}
Animate.restartOnHotReload = true;
runApp( // Edit from here...
FutureProvider<FragmentPrograms?>(
create: (context) => loadFragmentPrograms(),
initialData: null,
child: const NextGenApp(),
),
); // to here.
}
class NextGenApp extends StatelessWidget {
const NextGenApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
themeMode: ThemeMode.dark,
darkTheme: ThemeData(brightness: Brightness.dark),
home: const TitleScreen(),
);
}
}
provider
पैकेज औरstep_01
में शामिल शेडर यूटिलिटी का फ़ायदा पाने के लिए, आपको उन्हें इंपोर्ट करना होगा.lib/title_screen/title_screen_ui.dart
में नए इंपोर्ट जोड़ें. इसके लिए, यह तरीका अपनाएं:
lib/title_screen/title_screen_ui.dart
import 'package:extra_alignments/extra_alignments.dart';
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:focusable_control_builder/focusable_control_builder.dart';
import 'package:gap/gap.dart';
import 'package:provider/provider.dart'; // Add this import
import '../assets.dart';
import '../common/shader_effect.dart'; // And this import
import '../common/ticking_builder.dart'; // And this import
import '../common/ui_scaler.dart';
import '../styles.dart';
class TitleScreenUi extends StatelessWidget {
_TitleText
विजेट में बदलाव करके, टाइटल को शेडर के साथ इस तरह से बदलें:
lib/title_screen/title_screen_ui.dart
class _TitleText extends StatelessWidget {
const _TitleText();
@override
Widget build(BuildContext context) {
Widget content = Column( // Modify this line
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Gap(20),
Row(
mainAxisSize: MainAxisSize.min,
children: [
Transform.translate(
offset: Offset(-(TextStyles.h1.letterSpacing! * .5), 0),
child: Text('OUTPOST', style: TextStyles.h1),
),
Image.asset(AssetPaths.titleSelectedLeft, height: 65),
Text('57', style: TextStyles.h2),
Image.asset(AssetPaths.titleSelectedRight, height: 65),
],
).animate().fadeIn(delay: .8.seconds, duration: .7.seconds),
Text('INTO THE UNKNOWN', style: TextStyles.h3)
.animate()
.fadeIn(delay: 1.seconds, duration: .7.seconds),
],
);
return Consumer<FragmentPrograms?>( // Add from here...
builder: (context, fragmentPrograms, _) {
if (fragmentPrograms == null) return content;
return TickingBuilder(
builder: (context, time) {
return AnimatedSampler(
(image, size, canvas) {
const double overdrawPx = 30;
final shader = fragmentPrograms.ui.fragmentShader();
shader
..setFloat(0, size.width)
..setFloat(1, size.height)
..setFloat(2, time)
..setImageSampler(0, image);
Rect rect = Rect.fromLTWH(-overdrawPx, -overdrawPx,
size.width + overdrawPx, size.height + overdrawPx);
canvas.drawRect(rect, Paint()..shader = shader);
},
child: content,
);
},
);
},
); // to here.
}
}
आपको टाइटल में बदलाव दिखेगा. यह बदलाव, आपको डरावने भविष्य के बारे में सोचने पर मजबूर कर सकता है.
ओर्ब जोड़ना
अब विंडो के बीच में ऑर्ब जोड़ें. आपको स्टार्ट बटन में onPressed
कॉलबैक जोड़ना होगा.
lib/title_screen/title_screen_ui.dart
में,TitleScreenUi
में इस तरह बदलाव करें:
lib/title_screen/title_screen_ui.dart
class TitleScreenUi extends StatelessWidget {
const TitleScreenUi({
super.key,
required this.difficulty,
required this.onDifficultyPressed,
required this.onDifficultyFocused,
required this.onStartPressed, // Add this argument
});
final int difficulty;
final void Function(int difficulty) onDifficultyPressed;
final void Function(int? difficulty) onDifficultyFocused;
final VoidCallback onStartPressed; // Add this attribute
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 40, horizontal: 50),
child: Stack(
children: [
/// Title Text
const TopLeft(
child: UiScaler(
alignment: Alignment.topLeft,
child: _TitleText(),
),
),
/// Difficulty Btns
BottomLeft(
child: UiScaler(
alignment: Alignment.bottomLeft,
child: _DifficultyBtns(
difficulty: difficulty,
onDifficultyPressed: onDifficultyPressed,
onDifficultyFocused: onDifficultyFocused,
),
),
),
/// StartBtn
BottomRight(
child: UiScaler(
alignment: Alignment.bottomRight,
child: Padding(
padding: const EdgeInsets.only(bottom: 20, right: 40),
child: _StartBtn(onPressed: onStartPressed), // Edit this line
),
),
),
],
),
);
}
}
आपने स्टार्ट बटन में कॉलबैक का इस्तेमाल किया है. अब आपको lib/title_screen/title_screen.dart
फ़ाइल में कई बदलाव करने होंगे.
- इंपोर्ट किए गए डेटा में इस तरह बदलाव करें:
lib/title_screen/title_screen.dart
import 'dart:math'; // Add this import
import 'dart:ui'; // And this import
import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; // Add this import
import 'package:flutter_animate/flutter_animate.dart';
import '../assets.dart';
import '../orb_shader/orb_shader_config.dart'; // And this import
import '../orb_shader/orb_shader_widget.dart'; // And this import too
import '../styles.dart';
import 'title_screen_ui.dart';
class TitleScreen extends StatefulWidget {
_TitleScreenState
में बदलाव करके इसे यहां दिए गए फ़ॉर्मैट के मुताबिक बनाएं. क्लास के लगभग हर हिस्से में किसी न किसी तरह का बदलाव किया गया है.
lib/title_screen/title_screen.dart
class _TitleScreenState extends State<TitleScreen>
with SingleTickerProviderStateMixin {
final _orbKey = GlobalKey<OrbShaderWidgetState>();
/// Editable Settings
/// 0-1, receive lighting strength
final _minReceiveLightAmt = .35;
final _maxReceiveLightAmt = .7;
/// 0-1, emit lighting strength
final _minEmitLightAmt = .5;
final _maxEmitLightAmt = 1;
/// Internal
var _mousePos = Offset.zero;
Color get _emitColor =>
AppColors.emitColors[_difficultyOverride ?? _difficulty];
Color get _orbColor =>
AppColors.orbColors[_difficultyOverride ?? _difficulty];
/// Selected difficulty
int _difficulty = 0;
/// Focused difficulty (if any)
int? _difficultyOverride;
double _orbEnergy = 0;
double _minOrbEnergy = 0;
double get _finalReceiveLightAmt {
final light =
lerpDouble(_minReceiveLightAmt, _maxReceiveLightAmt, _orbEnergy) ?? 0;
return light + _pulseEffect.value * .05 * _orbEnergy;
}
double get _finalEmitLightAmt {
return lerpDouble(_minEmitLightAmt, _maxEmitLightAmt, _orbEnergy) ?? 0;
}
late final _pulseEffect = AnimationController(
vsync: this,
duration: _getRndPulseDuration(),
lowerBound: -1,
upperBound: 1,
);
Duration _getRndPulseDuration() => 100.ms + 200.ms * Random().nextDouble();
double _getMinEnergyForDifficulty(int difficulty) => switch (difficulty) {
1 => 0.3,
2 => 0.6,
_ => 0,
};
@override
void initState() {
super.initState();
_pulseEffect.forward();
_pulseEffect.addListener(_handlePulseEffectUpdate);
}
void _handlePulseEffectUpdate() {
if (_pulseEffect.status == AnimationStatus.completed) {
_pulseEffect.reverse();
_pulseEffect.duration = _getRndPulseDuration();
} else if (_pulseEffect.status == AnimationStatus.dismissed) {
_pulseEffect.duration = _getRndPulseDuration();
_pulseEffect.forward();
}
}
void _handleDifficultyPressed(int value) {
setState(() => _difficulty = value);
_bumpMinEnergy();
}
Future<void> _bumpMinEnergy([double amount = 0.1]) async {
setState(() {
_minOrbEnergy = _getMinEnergyForDifficulty(_difficulty) + amount;
});
await Future<void>.delayed(.2.seconds);
setState(() {
_minOrbEnergy = _getMinEnergyForDifficulty(_difficulty);
});
}
void _handleStartPressed() => _bumpMinEnergy(0.3);
void _handleDifficultyFocused(int? value) {
setState(() {
_difficultyOverride = value;
if (value == null) {
_minOrbEnergy = _getMinEnergyForDifficulty(_difficulty);
} else {
_minOrbEnergy = _getMinEnergyForDifficulty(value);
}
});
}
/// Update mouse position so the orbWidget can use it, doing it here prevents
/// btns from blocking the mouse-move events in the widget itself.
void _handleMouseMove(PointerHoverEvent e) {
setState(() {
_mousePos = e.localPosition;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Center(
child: MouseRegion(
onHover: _handleMouseMove,
child: _AnimatedColors(
orbColor: _orbColor,
emitColor: _emitColor,
builder: (_, orbColor, emitColor) {
return Stack(
children: [
/// Bg-Base
Image.asset(AssetPaths.titleBgBase),
/// Bg-Receive
_LitImage(
color: orbColor,
imgSrc: AssetPaths.titleBgReceive,
pulseEffect: _pulseEffect,
lightAmt: _finalReceiveLightAmt,
),
/// Orb
Positioned.fill(
child: Stack(
children: [
// Orb
OrbShaderWidget(
key: _orbKey,
mousePos: _mousePos,
minEnergy: _minOrbEnergy,
config: OrbShaderConfig(
ambientLightColor: orbColor,
materialColor: orbColor,
lightColor: orbColor,
),
onUpdate: (energy) => setState(() {
_orbEnergy = energy;
}),
),
],
),
),
/// Mg-Base
_LitImage(
imgSrc: AssetPaths.titleMgBase,
color: orbColor,
pulseEffect: _pulseEffect,
lightAmt: _finalReceiveLightAmt,
),
/// Mg-Receive
_LitImage(
imgSrc: AssetPaths.titleMgReceive,
color: orbColor,
pulseEffect: _pulseEffect,
lightAmt: _finalReceiveLightAmt,
),
/// Mg-Emit
_LitImage(
imgSrc: AssetPaths.titleMgEmit,
color: emitColor,
pulseEffect: _pulseEffect,
lightAmt: _finalEmitLightAmt,
),
/// Fg-Rocks
Image.asset(AssetPaths.titleFgBase),
/// Fg-Receive
_LitImage(
imgSrc: AssetPaths.titleFgReceive,
color: orbColor,
pulseEffect: _pulseEffect,
lightAmt: _finalReceiveLightAmt,
),
/// Fg-Emit
_LitImage(
imgSrc: AssetPaths.titleFgEmit,
color: emitColor,
pulseEffect: _pulseEffect,
lightAmt: _finalEmitLightAmt,
),
/// UI
Positioned.fill(
child: TitleScreenUi(
difficulty: _difficulty,
onDifficultyFocused: _handleDifficultyFocused,
onDifficultyPressed: _handleDifficultyPressed,
onStartPressed: _handleStartPressed,
),
),
],
).animate().fadeIn(duration: 1.seconds, delay: .3.seconds);
},
),
),
),
);
}
}
_LitImage
में इस तरह बदलाव करें:
lib/title_screen/title_screen.dart
class _LitImage extends StatelessWidget {
const _LitImage({
required this.color,
required this.imgSrc,
required this.pulseEffect, // Add this parameter
required this.lightAmt,
});
final Color color;
final String imgSrc;
final AnimationController pulseEffect; // Add this attribute
final double lightAmt;
@override
Widget build(BuildContext context) {
final hsl = HSLColor.fromColor(color);
return ListenableBuilder( // Edit from here...
listenable: pulseEffect,
builder: (context, child) {
return Image.asset(
imgSrc,
color: hsl.withLightness(hsl.lightness * lightAmt).toColor(),
colorBlendMode: BlendMode.modulate,
);
},
); // to here.
}
}
यह इस बदलाव का नतीजा है.
7. पार्टिकल ऐनिमेशन जोड़ना
इस चरण में, ऐप्लिकेशन में पल्सिंग मूवमेंट बनाने के लिए, पार्टिकल ऐनिमेशन जोड़े जाते हैं.
हर जगह पार्टिकल जोड़ें
- एक नई
lib/title_screen/particle_overlay.dart
फ़ाइल बनाएं. इसके बाद, यह कोड जोड़ें:
lib/title_screen/particle_overlay.dart
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:particle_field/particle_field.dart';
import 'package:rnd/rnd.dart';
class ParticleOverlay extends StatelessWidget {
const ParticleOverlay({super.key, required this.color, required this.energy});
final Color color;
final double energy;
@override
Widget build(BuildContext context) {
return ParticleField(
spriteSheet: SpriteSheet(
image: const AssetImage('assets/images/particle-wave.png'),
),
// blend the image's alpha with the specified color:
blendMode: BlendMode.dstIn,
// this runs every tick:
onTick: (controller, _, size) {
List<Particle> particles = controller.particles;
// add a new particle with random angle, distance & velocity:
double a = rnd(pi * 2);
double dist = rnd(1, 4) * 35 + 150 * energy;
double vel = rnd(1, 2) * (1 + energy * 1.8);
particles.add(Particle(
// how many ticks this particle will live:
lifespan: rnd(1, 2) * 20 + energy * 15,
// starting distance from center:
x: cos(a) * dist,
y: sin(a) * dist,
// starting velocity:
vx: cos(a) * vel,
vy: sin(a) * vel,
// other starting values:
rotation: a,
scale: rnd(1, 2) * 0.6 + energy * 0.5,
));
// update all of the particles:
for (int i = particles.length - 1; i >= 0; i--) {
Particle p = particles[i];
if (p.lifespan <= 0) {
// particle is expired, remove it:
particles.removeAt(i);
continue;
}
p.update(
scale: p.scale * 1.025,
vx: p.vx * 1.025,
vy: p.vy * 1.025,
color: color.withOpacity(p.lifespan * 0.001 + 0.01),
lifespan: p.lifespan - 1,
);
}
},
);
}
}
lib/title_screen/title_screen.dart
के लिए इंपोर्ट में इस तरह बदलाव करें:
lib/title_screen/title_screen.dart
import 'dart:math';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_animate/flutter_animate.dart';
import '../assets.dart';
import '../orb_shader/orb_shader_config.dart';
import '../orb_shader/orb_shader_widget.dart';
import '../styles.dart';
import 'particle_overlay.dart'; // Add this import
import 'title_screen_ui.dart';
class TitleScreen extends StatefulWidget {
_TitleScreenState
केbuild
तरीके में बदलाव करके, यूज़र इंटरफ़ेस (यूआई) मेंParticleOverlay
जोड़ें. इसके लिए, यह तरीका अपनाएं:
lib/title_screen/title_screen.dart
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Center(
child: MouseRegion(
onHover: _handleMouseMove,
child: _AnimatedColors(
orbColor: _orbColor,
emitColor: _emitColor,
builder: (_, orbColor, emitColor) {
return Stack(
children: [
/// Bg-Base
Image.asset(AssetPaths.titleBgBase),
/// Bg-Receive
_LitImage(
color: orbColor,
imgSrc: AssetPaths.titleBgReceive,
pulseEffect: _pulseEffect,
lightAmt: _finalReceiveLightAmt,
),
/// Orb
Positioned.fill(
child: Stack(
children: [
// Orb
OrbShaderWidget(
key: _orbKey,
mousePos: _mousePos,
minEnergy: _minOrbEnergy,
config: OrbShaderConfig(
ambientLightColor: orbColor,
materialColor: orbColor,
lightColor: orbColor,
),
onUpdate: (energy) => setState(() {
_orbEnergy = energy;
}),
),
],
),
),
/// Mg-Base
_LitImage(
imgSrc: AssetPaths.titleMgBase,
color: orbColor,
pulseEffect: _pulseEffect,
lightAmt: _finalReceiveLightAmt,
),
/// Mg-Receive
_LitImage(
imgSrc: AssetPaths.titleMgReceive,
color: orbColor,
pulseEffect: _pulseEffect,
lightAmt: _finalReceiveLightAmt,
),
/// Mg-Emit
_LitImage(
imgSrc: AssetPaths.titleMgEmit,
color: emitColor,
pulseEffect: _pulseEffect,
lightAmt: _finalEmitLightAmt,
),
/// Particle Field
Positioned.fill( // Add from here...
child: IgnorePointer(
child: ParticleOverlay(
color: orbColor,
energy: _orbEnergy,
),
),
), // to here.
/// Fg-Rocks
Image.asset(AssetPaths.titleFgBase),
/// Fg-Receive
_LitImage(
imgSrc: AssetPaths.titleFgReceive,
color: orbColor,
pulseEffect: _pulseEffect,
lightAmt: _finalReceiveLightAmt,
),
/// Fg-Emit
_LitImage(
imgSrc: AssetPaths.titleFgEmit,
color: emitColor,
pulseEffect: _pulseEffect,
lightAmt: _finalEmitLightAmt,
),
/// UI
Positioned.fill(
child: TitleScreenUi(
difficulty: _difficulty,
onDifficultyFocused: _handleDifficultyFocused,
onDifficultyPressed: _handleDifficultyPressed,
onStartPressed: _handleStartPressed,
),
),
],
).animate().fadeIn(duration: 1.seconds, delay: .3.seconds);
},
),
),
),
);
}
आखिरी नतीजे में, ऐनिमेशन, फ़्रैगमेंट शेडर, और पार्टिकल इफ़ेक्ट शामिल हैं. यह कई प्लैटफ़ॉर्म पर काम करता है!
हर जगह पार्टिकल जोड़ें. यहां तक कि वेब पर भी
इस कोड में एक छोटी सी समस्या है. वेब पर Flutter का इस्तेमाल करने पर, दो रेंडरिंग इंजन का इस्तेमाल किया जा सकता है: CanvasKit इंजन, जिसका इस्तेमाल डेस्कटॉप क्लास ब्राउज़र पर डिफ़ॉल्ट रूप से किया जाता है. दूसरा, एचटीएमएल डीओएम रेंडरर, जिसका इस्तेमाल मोबाइल डिवाइसों पर डिफ़ॉल्ट रूप से किया जाता है. समस्या यह है कि एचटीएमएल डीओएम रेंडरर, फ़्रैगमेंट शेडर के साथ काम नहीं करता.
इस समस्या को ठीक करने के लिए, सिर्फ़ CanvasKit रेंडरर का इस्तेमाल करके वेब के लिए ऐप्लिकेशन बनाएं. इसके लिए, बिल्ड कमांड में इस तरह से फ़्लैग जोड़ें:
$ flutter build web --web-renderer canvaskit Font asset "MaterialIcons-Regular.otf" was tree-shaken, reducing it from 1645184 to 7692 bytes (99.5% reduction). Tree-shaking can be disabled by providing the --no-tree-shake-icons flag when building your app. Font asset "CupertinoIcons.ttf" was tree-shaken, reducing it from 257628 to 1172 bytes (99.5% reduction). Tree-shaking can be disabled by providing the --no-tree-shake-icons flag when building your app. Compiling lib/main.dart for the Web... 15.6s ✓ Built build/web
यहां आपके सभी काम को Chrome ब्राउज़र में दिखाया गया है.
8. बधाई हो
आपने ऐनिमेशन, फ़्रैगमेंट शेडर, और पार्टिकल ऐनिमेशन की मदद से, गेम की पूरी जानकारी देने वाली इंट्रो स्क्रीन बनाई है! अब इन तकनीकों का इस्तेमाल, Flutter के साथ काम करने वाले सभी प्लैटफ़ॉर्म पर किया जा सकता है.
ज़्यादा जानें
flutter_animate
पैकेज देखें- फ़्रैगमेंट शेडर के लिए Flutter के साथ काम करने की सुविधा से जुड़ा दस्तावेज़ देखें
- पैट्रिसियो गोंज़ालेज़ विवो और जेन लो की द बुक ऑफ़ शेडर्स
- Shader toy, साथ मिलकर शेडर बनाने की सुविधा देने वाला एक प्लैटफ़ॉर्म
- simple_shader, Flutter फ़्रैगमेंट शेडर का सैंपल प्रोजेक्ट