fix: delay after fire
This commit is contained in:
@@ -18,6 +18,7 @@ class CardComponent extends PositionComponent
|
|||||||
VoidCallback? onPlay; // در صورت مجاز بودن، بازیِ این کارت
|
VoidCallback? onPlay; // در صورت مجاز بودن، بازیِ این کارت
|
||||||
bool dimmed; // کارت غیرمجاز/غیرفعال
|
bool dimmed; // کارت غیرمجاز/غیرفعال
|
||||||
Vector2? home; // موقعیت اصلی در دست (برای برگشت پس از کشیدنِ ناقص)
|
Vector2? home; // موقعیت اصلی در دست (برای برگشت پس از کشیدنِ ناقص)
|
||||||
|
double homeAngle = 0; // زاویهی بادبزنیِ کارت در دست (برای برگشت پس از کشیدن)
|
||||||
bool highlight = false; // کارتِ برندهی دست ⇒ درخششِ طلایی پیش از جمعشدن
|
bool highlight = false; // کارتِ برندهی دست ⇒ درخششِ طلایی پیش از جمعشدن
|
||||||
int restPriority = 0; // ترتیب لایهی اصلی در دست (برای بازگردانی پس از کشیدن)
|
int restPriority = 0; // ترتیب لایهی اصلی در دست (برای بازگردانی پس از کشیدن)
|
||||||
Rect? dropZone; // ناحیهی وسط میز؛ رهاکردن کارت در آن یعنی بازی
|
Rect? dropZone; // ناحیهی وسط میز؛ رهاکردن کارت در آن یعنی بازی
|
||||||
@@ -72,6 +73,13 @@ class CardComponent extends PositionComponent
|
|||||||
Vector2.all(s), EffectController(duration: 0.12, curve: Curves.easeOut)));
|
Vector2.all(s), EffectController(duration: 0.12, curve: Curves.easeOut)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// چرخاندنِ نرمِ کارت به زاویهی مشخص (صافکردن هنگام کشیدن، برگشت هنگام رها).
|
||||||
|
void _rotateTo(double a) {
|
||||||
|
children.whereType<RotateEffect>().toList().forEach((e) => e.removeFromParent());
|
||||||
|
add(RotateEffect.to(
|
||||||
|
a, EffectController(duration: 0.12, curve: Curves.easeOut)));
|
||||||
|
}
|
||||||
|
|
||||||
/// بازنشانیِ فوریِ مقیاس به اندازهی عادی (هنگام بازی روی میز).
|
/// بازنشانیِ فوریِ مقیاس به اندازهی عادی (هنگام بازی روی میز).
|
||||||
void resetScale() {
|
void resetScale() {
|
||||||
children.whereType<ScaleEffect>().toList().forEach((e) => e.removeFromParent());
|
children.whereType<ScaleEffect>().toList().forEach((e) => e.removeFromParent());
|
||||||
@@ -94,12 +102,15 @@ class CardComponent extends PositionComponent
|
|||||||
_dragging = true;
|
_dragging = true;
|
||||||
priority = 1000; // روی همهی کارتها
|
priority = 1000; // روی همهی کارتها
|
||||||
_scaleTo(_dragScale); // بزرگنماییِ نرم هنگام انتخاب
|
_scaleTo(_dragScale); // بزرگنماییِ نرم هنگام انتخاب
|
||||||
|
_rotateTo(0); // صافشدن: کارت دیگر با زاویهی بادبزنیِ دست کشیده نمیشود
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onDragUpdate(DragUpdateEvent event) {
|
void onDragUpdate(DragUpdateEvent event) {
|
||||||
if (!_dragging) return;
|
if (!_dragging) return;
|
||||||
position += event.localDelta;
|
// canvasDelta (فضای صفحه) نه localDelta: مستقل از چرخش/مقیاسِ خودِ کارت است،
|
||||||
|
// پس کارت دقیقاً بهدنبالِ انگشت میآید نه روی مسیرِ منحنیِ قوسِ دست.
|
||||||
|
position += event.canvasDelta;
|
||||||
_overZone = _inDropZone();
|
_overZone = _inDropZone();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,6 +150,7 @@ class CardComponent extends PositionComponent
|
|||||||
|
|
||||||
void _returnHome() {
|
void _returnHome() {
|
||||||
priority = restPriority; // بازگردانی ترتیب لایه تا روی کارتهای دیگر نیفتد
|
priority = restPriority; // بازگردانی ترتیب لایه تا روی کارتهای دیگر نیفتد
|
||||||
|
_rotateTo(homeAngle); // بازگشت به زاویهی بادبزنیِ دست
|
||||||
if (home == null) return;
|
if (home == null) return;
|
||||||
add(MoveToEffect(home!, EffectController(duration: 0.2, curve: Curves.easeOut)));
|
add(MoveToEffect(home!, EffectController(duration: 0.2, curve: Curves.easeOut)));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,14 @@ class HokmGame extends FlameGame {
|
|||||||
List<String> _prevTrick = const [];
|
List<String> _prevTrick = const [];
|
||||||
bool _highlightedTrick = false; // آیا کارتِ برندهی دستِ کامل هایلایت شده (یکبار)
|
bool _highlightedTrick = false; // آیا کارتِ برندهی دستِ کامل هایلایت شده (یکبار)
|
||||||
int _bestTrumpRank = 0; // بالاترین رتبهی حکمِ بازیشده در دستِ جاری (برای افکتِ بریدن)
|
int _bestTrumpRank = 0; // بالاترین رتبهی حکمِ بازیشده در دستِ جاری (برای افکتِ بریدن)
|
||||||
|
int _cutCombo = 0; // شمارِ بریدنِ پیاپیِ سریع (روبُریدنها) برای افکتِ آتش
|
||||||
|
double _lastCutAt = -999; // زمانِ آخرین بریدن (برای پنجرهی کمبو)
|
||||||
|
// «مکثِ آتش»: پس از افکتِ آتشِ کمبو، صحنه کوتاه فریز میشود تا لحظه دیده شود؛
|
||||||
|
// وضعیتهای ورودی بافر میشوند و پس از مکث اعمال میگردند (کاربر و بات فرقی ندارند).
|
||||||
|
bool _cutHold = false;
|
||||||
|
double _cutHoldUntil = 0;
|
||||||
|
GameState? _pendingState;
|
||||||
|
static const double _cutHoldSec = 1.0;
|
||||||
int _prevHandSize = 0; // برای تشخیصِ پخشِ کارت (دستِ جدید) جهت صدای بُر زدن
|
int _prevHandSize = 0; // برای تشخیصِ پخشِ کارت (دستِ جدید) جهت صدای بُر زدن
|
||||||
String _prevHandSig = ''; // امضای دست؛ برای تشخیصِ بُرِ مجدد (تغییرِ ۵ کارت در فازِ حکم)
|
String _prevHandSig = ''; // امضای دست؛ برای تشخیصِ بُرِ مجدد (تغییرِ ۵ کارت در فازِ حکم)
|
||||||
bool _silent = false; // هنگام خروج از میز، دیگر صدایی پخش نشود
|
bool _silent = false; // هنگام خروج از میز، دیگر صدایی پخش نشود
|
||||||
@@ -91,39 +99,12 @@ class HokmGame extends FlameGame {
|
|||||||
_sub = cubit.stream.listen((ui) {
|
_sub = cubit.stream.listen((ui) {
|
||||||
if (ui.state == null) return;
|
if (ui.state == null) return;
|
||||||
final s = ui.state!;
|
final s = ui.state!;
|
||||||
_checkTrickSounds(s);
|
// در حینِ «مکثِ آتش»، آخرین وضعیت را بافر کن و بعد از مکث اعمالش کن.
|
||||||
// شروعِ دستِ جدید (پخشِ کارت): انیمیشن و صدای بُر زدن.
|
if (_cutHold) {
|
||||||
// دستِ اول تا پایانِ نمایشِ «جستجوی حریف» به تعویق میافتد تا انیمیشن
|
_pendingState = s;
|
||||||
// زیرِ اوورلی پنهان نشود و صدا با تصویر هماهنگ باشد.
|
return;
|
||||||
if (_prevHandSize == 0 && s.yourHand.isNotEmpty && !_silent) {
|
|
||||||
if (_introActive) {
|
|
||||||
_pendingShuffle = true;
|
|
||||||
} else {
|
|
||||||
_shuffleNow();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// بُرِ مجدد: هنوز در فازِ حکم هستیم اما ۵ کارتِ حاکم عوض شده ⇒ انیمیشنِ بُر دوباره.
|
_applyState(s);
|
||||||
final sig = (s.yourHand.toList()..sort()).join(',');
|
|
||||||
if (!_silent &&
|
|
||||||
!_introActive &&
|
|
||||||
s.phase == 'choose_trump' &&
|
|
||||||
s.trump == null &&
|
|
||||||
_prevHandSize > 0 &&
|
|
||||||
_prevHandSig.isNotEmpty &&
|
|
||||||
sig != _prevHandSig) {
|
|
||||||
_shuffleNow();
|
|
||||||
}
|
|
||||||
_prevHandSig = sig;
|
|
||||||
// لحظهی انتخابِ حکم: صدا + نمایشِ بزرگِ حکم در مرکز.
|
|
||||||
if (_prevTrump == null && s.trump != null && !_silent) {
|
|
||||||
AppSounds.selectHokm();
|
|
||||||
_spawnTrumpReveal(s.trump!);
|
|
||||||
_dealAllNow(); // پخشِ بقیهی کارتها (بازی تا پایانِ آن قفل است)
|
|
||||||
}
|
|
||||||
_prevTrump = s.trump;
|
|
||||||
_prevHandSize = s.yourHand.length;
|
|
||||||
_s = s;
|
|
||||||
_relayout();
|
|
||||||
});
|
});
|
||||||
if (cubit.state.state != null) {
|
if (cubit.state.state != null) {
|
||||||
_prevTrick = cubit.state.state!.trick.map((t) => t.card).toList();
|
_prevTrick = cubit.state.state!.trick.map((t) => t.card).toList();
|
||||||
@@ -135,6 +116,44 @@ class HokmGame extends FlameGame {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// اعمالِ یک وضعیتِ بازی روی صحنه (صداها/انیمیشنها + چیدمانِ دوباره). در حالتِ
|
||||||
|
/// عادی مستقیم از استریم صدا زده میشود؛ در «مکثِ آتش» پس از پایانِ مکث.
|
||||||
|
void _applyState(GameState s) {
|
||||||
|
_checkTrickSounds(s);
|
||||||
|
// شروعِ دستِ جدید (پخشِ کارت): انیمیشن و صدای بُر زدن.
|
||||||
|
// دستِ اول تا پایانِ نمایشِ «جستجوی حریف» به تعویق میافتد تا انیمیشن
|
||||||
|
// زیرِ اوورلی پنهان نشود و صدا با تصویر هماهنگ باشد.
|
||||||
|
if (_prevHandSize == 0 && s.yourHand.isNotEmpty && !_silent) {
|
||||||
|
if (_introActive) {
|
||||||
|
_pendingShuffle = true;
|
||||||
|
} else {
|
||||||
|
_shuffleNow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// بُرِ مجدد: هنوز در فازِ حکم هستیم اما ۵ کارتِ حاکم عوض شده ⇒ انیمیشنِ بُر دوباره.
|
||||||
|
final sig = (s.yourHand.toList()..sort()).join(',');
|
||||||
|
if (!_silent &&
|
||||||
|
!_introActive &&
|
||||||
|
s.phase == 'choose_trump' &&
|
||||||
|
s.trump == null &&
|
||||||
|
_prevHandSize > 0 &&
|
||||||
|
_prevHandSig.isNotEmpty &&
|
||||||
|
sig != _prevHandSig) {
|
||||||
|
_shuffleNow();
|
||||||
|
}
|
||||||
|
_prevHandSig = sig;
|
||||||
|
// لحظهی انتخابِ حکم: صدا + نمایشِ بزرگِ حکم در مرکز.
|
||||||
|
if (_prevTrump == null && s.trump != null && !_silent) {
|
||||||
|
AppSounds.selectHokm();
|
||||||
|
_spawnTrumpReveal(s.trump!);
|
||||||
|
_dealAllNow(); // پخشِ بقیهی کارتها (بازی تا پایانِ آن قفل است)
|
||||||
|
}
|
||||||
|
_prevTrump = s.trump;
|
||||||
|
_prevHandSize = s.yourHand.length;
|
||||||
|
_s = s;
|
||||||
|
_relayout();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onGameResize(Vector2 size) {
|
void onGameResize(Vector2 size) {
|
||||||
super.onGameResize(size);
|
super.onGameResize(size);
|
||||||
@@ -176,8 +195,20 @@ class HokmGame extends FlameGame {
|
|||||||
_bestTrumpRank = rank;
|
_bestTrumpRank = rank;
|
||||||
_shake = 1.0;
|
_shake = 1.0;
|
||||||
add(Lightning());
|
add(Lightning());
|
||||||
|
// کمبوِ بریدن: روبُریدنهای سریعِ پیاپی (پنجرهی ~۶ ثانیه) را میشمارد.
|
||||||
|
_cutCombo = (_t - _lastCutAt < 6.0) ? _cutCombo + 1 : 1;
|
||||||
|
_lastCutAt = _t;
|
||||||
if (!AppSettings.I.lowGraphics.value) {
|
if (!AppSettings.I.lowGraphics.value) {
|
||||||
add(CutBurst()); // فلاش + شوکویو + جرقه (کممصرف: خاموش)
|
add(CutBurst()); // فلاش + شوکویو + جرقه (کممصرف: خاموش)
|
||||||
|
// سه بریدنِ پیاپی یا بیشتر ⇒ افکتِ آتش (هرچه کمبو بیشتر، شعله بلندتر).
|
||||||
|
if (_cutCombo >= 3) {
|
||||||
|
_shake = 1.4; // تکانِ قویتر برای اوجِ کمبو
|
||||||
|
add(FireBurst(intensity: _cutCombo));
|
||||||
|
// مکثِ کوتاه پس از آتش تا لحظه دیده شود؛ کارتِ بعدی (کاربر یا بات)
|
||||||
|
// تا پایانِ مکث اعمال نمیشود.
|
||||||
|
_cutHold = true;
|
||||||
|
_cutHoldUntil = _t + _cutHoldSec;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
AppSounds.cut(); // بریدن با حکم (شمشیر)
|
AppSounds.cut(); // بریدن با حکم (شمشیر)
|
||||||
HapticFeedback.heavyImpact(); // ضربهی لمسیِ بُرِش با حکم
|
HapticFeedback.heavyImpact(); // ضربهی لمسیِ بُرِش با حکم
|
||||||
@@ -193,6 +224,8 @@ class HokmGame extends FlameGame {
|
|||||||
/// قطعِ صداهای بازی هنگام خروج از میز (جلوگیری از پخشِ صدای کارت پس از ترک).
|
/// قطعِ صداهای بازی هنگام خروج از میز (جلوگیری از پخشِ صدای کارت پس از ترک).
|
||||||
void silence() {
|
void silence() {
|
||||||
_silent = true;
|
_silent = true;
|
||||||
|
_cutHold = false; // مکثِ آتش را لغو کن تا وضعیتِ بافرشده اعمال/رها نشود
|
||||||
|
_pendingState = null;
|
||||||
AppSounds.stopHeartBeat();
|
AppSounds.stopHeartBeat();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,6 +309,19 @@ class HokmGame extends FlameGame {
|
|||||||
void update(double dt) {
|
void update(double dt) {
|
||||||
super.update(dt);
|
super.update(dt);
|
||||||
_t += dt;
|
_t += dt;
|
||||||
|
// پایانِ «مکثِ آتش»: آخرین وضعیتِ بافرشده را اعمال کن (یا صحنه را تازه کن).
|
||||||
|
if (_cutHold && _t >= _cutHoldUntil) {
|
||||||
|
_cutHold = false;
|
||||||
|
final p = _pendingState;
|
||||||
|
_pendingState = null;
|
||||||
|
if (!_silent) {
|
||||||
|
if (p != null) {
|
||||||
|
_applyState(p);
|
||||||
|
} else if (_s != null) {
|
||||||
|
_relayout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (_shake > 0) _shake = math.max(0, _shake - dt * 2.2);
|
if (_shake > 0) _shake = math.max(0, _shake - dt * 2.2);
|
||||||
_updateHeartBeat(dt);
|
_updateHeartBeat(dt);
|
||||||
}
|
}
|
||||||
@@ -376,8 +422,8 @@ class HokmGame extends FlameGame {
|
|||||||
// آیا این کارت در نوبتِ فعلی قابل بازی است (با رعایت follow-suit)؟
|
// آیا این کارت در نوبتِ فعلی قابل بازی است (با رعایت follow-suit)؟
|
||||||
bool _legal(String code) {
|
bool _legal(String code) {
|
||||||
final s = _s!;
|
final s = _s!;
|
||||||
// هنگامِ اینترو/بُر زدن/پخشِ کارتها، بازیِ کارت مجاز نیست.
|
// هنگامِ اینترو/بُر زدن/پخشِ کارتها یا مکثِ آتش، بازیِ کارت مجاز نیست.
|
||||||
if (_introActive || _shuffling || _dealing) return false;
|
if (_introActive || _shuffling || _dealing || _cutHold) return false;
|
||||||
if (s.phase != 'playing' || s.trickDone || !s.isMyTurn) return false;
|
if (s.phase != 'playing' || s.trickDone || !s.isMyTurn) return false;
|
||||||
final lead = s.leadSuit;
|
final lead = s.leadSuit;
|
||||||
if (lead == null || lead.isEmpty) return true;
|
if (lead == null || lead.isEmpty) return true;
|
||||||
@@ -471,6 +517,7 @@ class HokmGame extends FlameGame {
|
|||||||
c.size = Vector2(hw, hh);
|
c.size = Vector2(hw, hh);
|
||||||
}
|
}
|
||||||
c.angle = a; // چرخشِ کارت همراستا با شعاعِ قوس
|
c.angle = a; // چرخشِ کارت همراستا با شعاعِ قوس
|
||||||
|
c.homeAngle = a; // زاویهی مرجع برای بازگشت پس از کشیدنِ ناقص
|
||||||
c.onPlay = legal ? () => cubit.playCard(code) : null;
|
c.onPlay = legal ? () => cubit.playCard(code) : null;
|
||||||
c.dimmed = s.phase == 'playing' && s.isMyTurn && !legal;
|
c.dimmed = s.phase == 'playing' && s.isMyTurn && !legal;
|
||||||
c.home = target;
|
c.home = target;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
import 'package:flame/components.dart';
|
import 'package:flame/components.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -327,6 +328,128 @@ class _Spark {
|
|||||||
_Spark(this.ang, this.speed, this.r);
|
_Spark(this.ang, this.speed, this.r);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// افکتِ آتش برای «بریدنِ پیاپیِ حکم» (کمبو: چند روبُریدنِ سریع در یک دست).
|
||||||
|
/// شعلههایی که از مرکزِ میز بالا میآیند + ذراتِ اخگرِ صعودی. هر چه شدتِ کمبو
|
||||||
|
/// بیشتر (تعدادِ بریدن) شعله بلندتر و پرحجمتر است.
|
||||||
|
class FireBurst extends PositionComponent with HasGameReference {
|
||||||
|
/// شدت (۱..۳+): تعدادِ بریدنِ پیاپی؛ ارتفاع/حجمِ شعله را تعیین میکند.
|
||||||
|
final int intensity;
|
||||||
|
FireBurst({this.intensity = 3});
|
||||||
|
|
||||||
|
static const _max = 1.15;
|
||||||
|
double _life = _max;
|
||||||
|
double _t = 0;
|
||||||
|
final _rng = math.Random();
|
||||||
|
late final List<_Ember> _embers;
|
||||||
|
late final List<_Flame> _flames;
|
||||||
|
|
||||||
|
double get _scale => (intensity.clamp(3, 6)) / 3.0; // ۱.۰ در ۳ بریدن، بیشتر با کمبو
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
size = game.size;
|
||||||
|
final n = (18 * _scale).round();
|
||||||
|
_embers = List.generate(n, (_) {
|
||||||
|
final spread = (_rng.nextDouble() - 0.5) * size.x * 0.34;
|
||||||
|
final rise = 0.30 + _rng.nextDouble() * 0.55; // کسری از ارتفاعِ صعود
|
||||||
|
final delay = _rng.nextDouble() * 0.35;
|
||||||
|
return _Ember(spread, rise, delay, 1.5 + _rng.nextDouble() * 2.8);
|
||||||
|
});
|
||||||
|
final f = (5 * _scale).round();
|
||||||
|
_flames = List.generate(f, (i) {
|
||||||
|
final off = (_rng.nextDouble() - 0.5) * size.x * 0.22;
|
||||||
|
final w = size.x * (0.07 + _rng.nextDouble() * 0.06);
|
||||||
|
final h = size.x * (0.18 + _rng.nextDouble() * 0.12) * _scale;
|
||||||
|
final ph = _rng.nextDouble() * math.pi * 2;
|
||||||
|
return _Flame(off, w, h, ph);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void update(double dt) {
|
||||||
|
_t += dt;
|
||||||
|
_life -= dt;
|
||||||
|
if (_life <= 0) removeFromParent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void render(Canvas canvas) {
|
||||||
|
final prog = (1 - _life / _max).clamp(0.0, 1.0);
|
||||||
|
// پاکتِ شدت: سریع بالا میآید، آرام محو میشود.
|
||||||
|
final env = prog < 0.2 ? prog / 0.2 : (1 - (prog - 0.2) / 0.8);
|
||||||
|
final base = Offset(size.x / 2, size.y * 0.56); // کفِ شعله (نزدیکِ مرکزِ میز)
|
||||||
|
|
||||||
|
// ۱) هالهی نارنجیِ گرم پشتِ شعله.
|
||||||
|
canvas.drawCircle(
|
||||||
|
base,
|
||||||
|
size.x * 0.28 * env * _scale,
|
||||||
|
Paint()
|
||||||
|
..color = const Color(0xFFFF7A18).withValues(alpha: 0.22 * env)
|
||||||
|
..maskFilter = MaskFilter.blur(BlurStyle.normal, size.x * 0.05),
|
||||||
|
);
|
||||||
|
|
||||||
|
// ۲) زبانههای شعله (نارنجی→زرد) با لرزشِ افقی.
|
||||||
|
for (final fl in _flames) {
|
||||||
|
final sway = math.sin(_t * 9 + fl.phase) * size.x * 0.02;
|
||||||
|
final cx = base.dx + fl.off + sway;
|
||||||
|
final h = fl.h * env;
|
||||||
|
final tip = Offset(cx + sway * 0.6, base.dy - h);
|
||||||
|
final path = Path()
|
||||||
|
..moveTo(cx - fl.w / 2, base.dy)
|
||||||
|
..quadraticBezierTo(cx - fl.w * 0.5, base.dy - h * 0.5, tip.dx, tip.dy)
|
||||||
|
..quadraticBezierTo(cx + fl.w * 0.5, base.dy - h * 0.5, cx + fl.w / 2, base.dy)
|
||||||
|
..close();
|
||||||
|
canvas.drawPath(
|
||||||
|
path,
|
||||||
|
Paint()
|
||||||
|
..shader = _flameShader(base.dy, base.dy - h, env)
|
||||||
|
..maskFilter = MaskFilter.blur(BlurStyle.normal, size.x * 0.008),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ۳) اخگرهای صعودی.
|
||||||
|
for (final e in _embers) {
|
||||||
|
final lt = ((prog - e.delay) / (1 - e.delay)).clamp(0.0, 1.0);
|
||||||
|
if (lt <= 0) continue;
|
||||||
|
final y = base.dy - lt * size.y * 0.42 * e.rise * _scale;
|
||||||
|
final x = base.dx + e.spread * lt + math.sin(lt * 8 + e.spread) * 3;
|
||||||
|
final a = (1 - lt) * env;
|
||||||
|
canvas.drawCircle(
|
||||||
|
Offset(x, y),
|
||||||
|
e.r * (1 - lt * 0.5),
|
||||||
|
Paint()
|
||||||
|
..color = Color.lerp(
|
||||||
|
const Color(0xFFFFE082),
|
||||||
|
const Color(0xFFFF5722),
|
||||||
|
lt,
|
||||||
|
)!.withValues(alpha: a),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// گرادیانِ عمودیِ شعله: کفِ زرد/سفید (y0) → نوکِ نارنجیِ کمرنگ (y1).
|
||||||
|
Shader _flameShader(double y0, double y1, double env) => ui.Gradient.linear(
|
||||||
|
Offset(0, y0),
|
||||||
|
Offset(0, y1),
|
||||||
|
[
|
||||||
|
const Color(0xFFFFF3C4).withValues(alpha: 0.95 * env),
|
||||||
|
const Color(0xFFFFB300).withValues(alpha: 0.85 * env),
|
||||||
|
const Color(0xFFFF5722).withValues(alpha: 0.15 * env),
|
||||||
|
],
|
||||||
|
const [0.0, 0.45, 1.0],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _Ember {
|
||||||
|
final double spread, rise, delay, r;
|
||||||
|
_Ember(this.spread, this.rise, this.delay, this.r);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _Flame {
|
||||||
|
final double off, w, h, phase;
|
||||||
|
_Flame(this.off, this.w, this.h, this.phase);
|
||||||
|
}
|
||||||
|
|
||||||
class Lightning extends PositionComponent with HasGameReference {
|
class Lightning extends PositionComponent with HasGameReference {
|
||||||
static const _max = 0.55;
|
static const _max = 0.55;
|
||||||
double _life = _max;
|
double _life = _max;
|
||||||
|
|||||||
Reference in New Issue
Block a user