Juice pass: procedural SFX, particles, polished visuals
Sound (SfxManager.cs): - Procedural audio synthesis via AudioStreamWav — no external files - Distinct tones for place, produce, transfer, deliver, move, destroy, victory - Simple ADSR envelope, sine/triangle waveforms, filtered noise for swooshes Pieces (PieceView.cs): - Warm earthy palette: sage green, deep teal, dusty rose, burnt sienna - Drop shadow under each piece for depth - 3-stop radial gradient (bright center → main → dark rim) - Scale bounce on placement (0 → 1.15 → 1.0 with back-out easing) - Cargo indicator pulses gently when carrying Trajectories (TrajectView.cs): - Arrowhead at endpoint showing movement direction - Antialiased lines with piece-matched colors Cells (CellView.cs): - Warmer palette: parchment/walnut board, deep forest production, aged gold demand - Production flash uses warm golden glow instead of white - Subtle inner shadow for visual depth Animations (EventAnimator.cs): - Production: golden particles burst from production cells - Transfer: cargo slides with 2-particle trail + back-out whip easing - Destruction: pieces shrink + spin + red particle explosion - Victory: 40 confetti particles rain across the screen - All phases trigger appropriate SFX UI polish: - ControlBar: styled buttons with rounded corners, disabled states - MetricsOverlay: fade-in + scale animation, sequential metric reveals - ObjectivePanel: animated progress bars, styled fills, green flash on completion - Main: fade-in/out transitions between level select and gameplay Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e1218b3eaa
commit
450c069854
11 changed files with 874 additions and 174 deletions
|
|
@ -24,3 +24,9 @@ Input → Command → GameSim (state + rules) → Events → Presentation
|
|||
Tout `Control` (ColorRect, Label…) a `MouseFilter = Stop` par defaut. Quand un Control est enfant d'un Node2D (ex: les ColorRect dans CellView, les Labels dans PieceView), **il participe quand meme au systeme GUI et consomme les clics**, empechant `_UnhandledInput` de recevoir l'evenement.
|
||||
|
||||
**Regle** : toujours mettre `MouseFilter = Control.MouseFilterEnum.Ignore` sur les Controls purement visuels enfants de Node2D.
|
||||
|
||||
## Conventions Claude
|
||||
|
||||
### Plans
|
||||
|
||||
Les fichiers de plan doivent etre rediges a la racine du workspace (ex: `/workspace/PLAN_juice.md`), **pas** dans `.claude/plans/` car ce dossier a une taille limitee.
|
||||
|
|
|
|||
107
PLAN_juice.md
Normal file
107
PLAN_juice.md
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
# Plan: Juice Pass - Animations, Sons, Visuels
|
||||
|
||||
## Context
|
||||
|
||||
Le jeu fonctionne mais manque de "juice" — les interactions sont plates, pas de sons, les animations sont minimales. L'objectif est de rendre chaque action satisfaisante visuellement et auditivement.
|
||||
|
||||
## Principes
|
||||
|
||||
- Pas de dépendance externe (pas de fichiers audio .wav/.ogg) — sons générés procéduralement via `AudioStreamGenerator` ou les nœuds Godot (`AudioStreamPlayer` avec des tones synthétiques)
|
||||
- Tout est fait en code (pas de .tscn supplémentaires)
|
||||
- Les effets sont subtils, jamais bloquants
|
||||
|
||||
## Changements par catégorie
|
||||
|
||||
### 1. Pièces — Vie et feedback (PieceView.cs)
|
||||
|
||||
- **Bounce à la pose** : quand une pièce est placée, scale 0→1.2→1.0 (0.2s, ease back-out)
|
||||
- **Pulse quand porte un colis** : le cargo indicator pulse doucement (scale 1.0↔1.2, loop)
|
||||
- **Ombre sous la pièce** : un cercle sombre semi-transparent (alpha 0.15) légèrement décalé en bas
|
||||
- **Lettre de la pièce** : utiliser le nom complet court au lieu de la lettre seule pour plus de clarté
|
||||
|
||||
### 2. Trajectoires (TrajectView.cs)
|
||||
|
||||
- **Flèche directionnelle** : ajouter un triangle au bout de la ligne pour montrer le sens
|
||||
- **Pulse pendant la simulation** : la ligne pulse (alpha oscille 0.3↔0.6) quand la sim tourne
|
||||
- **Couleur par type de pièce** : la trajectoire reprend la couleur de la pièce
|
||||
|
||||
### 3. Animations de tour (EventAnimator.cs)
|
||||
|
||||
- **Production** : particules (petits carrés colorés) qui jaillissent de la cellule de production + scale bounce de la cellule
|
||||
- **Transfert** : le cargo slide laisse une traînée (2-3 carrés plus petits qui suivent avec délai) + ease back-out pour un effet de "whip"
|
||||
- **Mouvement** : les pièces se soulèvent légèrement (scale 1.0→1.1→1.0 pendant le déplacement) pour donner l'impression de vol
|
||||
- **Destruction** : la pièce se réduit (scale→0) + rotation + particules rouges éclatantes au lieu d'un simple flash
|
||||
- **Victoire** : pluie de confettis dorés sur tout l'écran
|
||||
|
||||
### 4. Cellules (CellView.cs)
|
||||
|
||||
- **Hover amélioré** : la cellule survolée fait un léger scale-up (1.0→1.03) + outline pulse
|
||||
- **Highlight de placement** : les cellules valides pulsent doucement (alpha oscille)
|
||||
|
||||
### 5. UI — Contrôles et panneaux
|
||||
|
||||
**ControlBar.cs** :
|
||||
- Boutons avec style (fond coloré, coins arrondis) au lieu du style par défaut Godot
|
||||
- Boutons disabled grayed out visuellement
|
||||
|
||||
**MetricsOverlay.cs** :
|
||||
- Apparition avec fade-in + scale (0.8→1.0) au lieu d'un Visible=true brutal
|
||||
- Les métriques apparaissent une par une avec un petit délai
|
||||
|
||||
**LevelSelectScreen.cs** :
|
||||
- Cards hover : légère élévation (border-color plus clair) + scale 1.0→1.02
|
||||
|
||||
**ObjectivePanel.cs** :
|
||||
- Flash vert sur la barre de progression quand une livraison arrive
|
||||
- Animation de la jauge (tween de la valeur plutôt qu'un saut)
|
||||
|
||||
### 6. Sons procéduraux (nouveau: SfxManager.cs)
|
||||
|
||||
Un nœud singleton qui génère des bips synthétiques via `AudioStreamPlayer` :
|
||||
- **Placement** : bip court montant (C5, 0.08s)
|
||||
- **Production** : bip grave doux (C3, 0.1s)
|
||||
- **Transfert** : swoosh (bruit blanc filtré, 0.15s)
|
||||
- **Livraison à demande** : ding satisfaisant (C5+E5 chord, 0.2s)
|
||||
- **Mouvement** : léger whoosh (bruit blanc très court, 0.05s)
|
||||
- **Destruction** : crunch descendant (C4→C2, 0.15s)
|
||||
- **Victoire** : arpège majeur montant (C4-E4-G4-C5, 0.5s)
|
||||
- **Clic UI** : tick léger (0.02s)
|
||||
|
||||
Implémenté avec `AudioStreamGenerator` pour les tones, buffer rempli avec des sinusoïdes + enveloppe ADSR simple.
|
||||
|
||||
### 7. Transitions (Main.cs)
|
||||
|
||||
- **Fade in/out** entre level select et gameplay : ColorRect noir plein écran, alpha 1→0 (0.4s)
|
||||
- **Camera** : zoom léger quand la simulation démarre (1.0→0.95→1.0)
|
||||
|
||||
## Fichiers à modifier
|
||||
|
||||
| Fichier | Changements |
|
||||
|---------|-------------|
|
||||
| `Scripts/Pieces/PieceView.cs` | Ombre, bounce, cargo pulse |
|
||||
| `Scripts/Pieces/TrajectView.cs` | Flèche, pulse, couleur |
|
||||
| `Scripts/Presentation/EventAnimator.cs` | Particules, trails, destruction améliorée, confettis victoire |
|
||||
| `Scripts/Board/CellView.cs` | Hover scale, highlight pulse |
|
||||
| `Scripts/UI/ControlBar.cs` | Boutons stylés |
|
||||
| `Scripts/UI/MetricsOverlay.cs` | Fade-in, métriques séquentielles |
|
||||
| `Scripts/UI/LevelSelectScreen.cs` | Card hover effects |
|
||||
| `Scripts/UI/ObjectivePanel.cs` | Flash vert, tween jauge |
|
||||
| `Scripts/Main.cs` | Fade transition, camera juice |
|
||||
| `Scripts/Presentation/SfxManager.cs` | **NOUVEAU** — sons procéduraux |
|
||||
|
||||
## Ordre d'implémentation
|
||||
|
||||
1. SfxManager (fondation audio)
|
||||
2. PieceView (bounce, ombre, cargo pulse)
|
||||
3. EventAnimator (particules, trails, destruction, confettis)
|
||||
4. CellView (hover, highlight pulse)
|
||||
5. TrajectView (flèche, pulse)
|
||||
6. UI polish (ControlBar, MetricsOverlay, ObjectivePanel, LevelSelectScreen)
|
||||
7. Transitions (Main.cs fade, camera)
|
||||
|
||||
## Vérification
|
||||
|
||||
- Lancer le jeu, vérifier chaque animation visuellement
|
||||
- S'assurer que les sons ne sont pas trop forts ou gênants
|
||||
- Vérifier que les animations n'interfèrent pas avec le gameplay (pas de blocage)
|
||||
- `dotnet test` pour s'assurer que l'engine n'est pas impacté
|
||||
|
|
@ -7,6 +7,7 @@ public partial class CellView : Node2D
|
|||
{
|
||||
private ColorRect _background = null!;
|
||||
private ColorRect _highlight = null!;
|
||||
private ColorRect _innerShadow = null!;
|
||||
private Label _label = null!;
|
||||
|
||||
// Hover outline (4 thin rects forming a border)
|
||||
|
|
@ -17,13 +18,14 @@ public partial class CellView : Node2D
|
|||
|
||||
public Coords Coords { get; private set; }
|
||||
|
||||
private static readonly Color LightColor = new("#F0D9B5");
|
||||
private static readonly Color DarkColor = new("#B58863");
|
||||
private static readonly Color WallColor = new("#555555");
|
||||
private static readonly Color ProductionColor = new("#6B8E5A");
|
||||
private static readonly Color DemandColor = new("#C9A833");
|
||||
// Warmer, more grounded palette
|
||||
private static readonly Color LightColor = new("#E8D5A8"); // warm parchment
|
||||
private static readonly Color DarkColor = new("#A07850"); // warm walnut
|
||||
private static readonly Color WallColor = new("#3A3A3A"); // charcoal
|
||||
private static readonly Color ProductionColor = new("#4A6E3A"); // deep forest
|
||||
private static readonly Color DemandColor = new("#B8942A"); // aged gold
|
||||
private static readonly Color HighlightColor = new("#44FF4444");
|
||||
private static readonly Color HoverOutlineColor = new("#FFFFFFAA");
|
||||
private static readonly Color HoverOutlineColor = new("#FFFFFF88");
|
||||
|
||||
private const int OutlineWidth = 2;
|
||||
|
||||
|
|
@ -49,6 +51,17 @@ public partial class CellView : Node2D
|
|||
};
|
||||
AddChild(_background);
|
||||
|
||||
// Subtle inner shadow for depth (top-left lit, bottom-right dark)
|
||||
_innerShadow = new ColorRect
|
||||
{
|
||||
Size = new Vector2(cellSize, cellSize),
|
||||
Position = Vector2.Zero,
|
||||
Color = new Color(0, 0, 0, 0.06f),
|
||||
Visible = cellType == CellType.Empty,
|
||||
MouseFilter = Control.MouseFilterEnum.Ignore
|
||||
};
|
||||
AddChild(_innerShadow);
|
||||
|
||||
_highlight = new ColorRect
|
||||
{
|
||||
Size = new Vector2(cellSize, cellSize),
|
||||
|
|
@ -60,56 +73,36 @@ public partial class CellView : Node2D
|
|||
AddChild(_highlight);
|
||||
|
||||
// Hover outline (4 border rects)
|
||||
_hoverTop = new ColorRect
|
||||
{
|
||||
Size = new Vector2(cellSize, OutlineWidth),
|
||||
Position = Vector2.Zero,
|
||||
Color = HoverOutlineColor,
|
||||
Visible = false,
|
||||
MouseFilter = Control.MouseFilterEnum.Ignore
|
||||
};
|
||||
AddChild(_hoverTop);
|
||||
|
||||
_hoverBottom = new ColorRect
|
||||
{
|
||||
Size = new Vector2(cellSize, OutlineWidth),
|
||||
Position = new Vector2(0, cellSize - OutlineWidth),
|
||||
Color = HoverOutlineColor,
|
||||
Visible = false,
|
||||
MouseFilter = Control.MouseFilterEnum.Ignore
|
||||
};
|
||||
AddChild(_hoverBottom);
|
||||
|
||||
_hoverLeft = new ColorRect
|
||||
{
|
||||
Size = new Vector2(OutlineWidth, cellSize),
|
||||
Position = Vector2.Zero,
|
||||
Color = HoverOutlineColor,
|
||||
Visible = false,
|
||||
MouseFilter = Control.MouseFilterEnum.Ignore
|
||||
};
|
||||
AddChild(_hoverLeft);
|
||||
|
||||
_hoverRight = new ColorRect
|
||||
{
|
||||
Size = new Vector2(OutlineWidth, cellSize),
|
||||
Position = new Vector2(cellSize - OutlineWidth, 0),
|
||||
Color = HoverOutlineColor,
|
||||
Visible = false,
|
||||
MouseFilter = Control.MouseFilterEnum.Ignore
|
||||
};
|
||||
AddChild(_hoverRight);
|
||||
_hoverTop = CreateBorderRect(new Vector2(cellSize, OutlineWidth), Vector2.Zero);
|
||||
_hoverBottom = CreateBorderRect(new Vector2(cellSize, OutlineWidth), new Vector2(0, cellSize - OutlineWidth));
|
||||
_hoverLeft = CreateBorderRect(new Vector2(OutlineWidth, cellSize), Vector2.Zero);
|
||||
_hoverRight = CreateBorderRect(new Vector2(OutlineWidth, cellSize), new Vector2(cellSize - OutlineWidth, 0));
|
||||
|
||||
_label = new Label
|
||||
{
|
||||
Position = new Vector2(2, 2),
|
||||
Position = new Vector2(4, 3),
|
||||
Text = "",
|
||||
MouseFilter = Control.MouseFilterEnum.Ignore
|
||||
};
|
||||
_label.AddThemeFontSizeOverride("font_size", 10);
|
||||
_label.AddThemeFontSizeOverride("font_size", 9);
|
||||
_label.AddThemeColorOverride("font_color", new Color(1, 1, 1, 0.7f));
|
||||
AddChild(_label);
|
||||
}
|
||||
|
||||
private ColorRect CreateBorderRect(Vector2 size, Vector2 pos)
|
||||
{
|
||||
var rect = new ColorRect
|
||||
{
|
||||
Size = size,
|
||||
Position = pos,
|
||||
Color = HoverOutlineColor,
|
||||
Visible = false,
|
||||
MouseFilter = Control.MouseFilterEnum.Ignore
|
||||
};
|
||||
AddChild(rect);
|
||||
return rect;
|
||||
}
|
||||
|
||||
public void SetLabel(string text) => _label.Text = text;
|
||||
public void SetHighlight(bool on) => _highlight.Visible = on;
|
||||
|
||||
|
|
@ -128,14 +121,15 @@ public partial class CellView : Node2D
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Brief white flash on the cell to signal production.
|
||||
/// Production pulse: warm glow that radiates outward.
|
||||
/// </summary>
|
||||
public void FlashProduce(float duration = 0.3f)
|
||||
{
|
||||
_highlight.Color = new Color(1, 1, 1, 0.5f);
|
||||
_highlight.Color = new Color(0.9f, 0.85f, 0.5f, 0.55f); // warm golden
|
||||
_highlight.Visible = true;
|
||||
var tween = CreateTween();
|
||||
tween.TweenProperty(_highlight, "color", new Color(1, 1, 1, 0f), duration);
|
||||
tween.TweenProperty(_highlight, "color", new Color(0.9f, 0.85f, 0.5f, 0f), duration)
|
||||
.SetEase(Tween.EaseType.Out).SetTrans(Tween.TransitionType.Cubic);
|
||||
tween.TweenCallback(Callable.From(() => _highlight.Visible = false));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using Godot;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Chessistics.Engine.Commands;
|
||||
|
|
@ -37,6 +38,7 @@ public partial class Main : Node2D
|
|||
private PanelContainer _sidePanel = null!;
|
||||
private PanelContainer _controlBarWrapper = null!;
|
||||
private Camera2D _camera = null!;
|
||||
private ColorRect _fadeOverlay = null!;
|
||||
|
||||
// Simulation timer
|
||||
private Godot.Timer _simTimer = null!;
|
||||
|
|
@ -58,6 +60,9 @@ public partial class Main : Node2D
|
|||
BuildSceneTree();
|
||||
ConnectSignals();
|
||||
ShowLevelSelect();
|
||||
|
||||
// Fade in from black on startup
|
||||
FadeIn(0.5f);
|
||||
}
|
||||
|
||||
public override void _UnhandledInput(InputEvent @event)
|
||||
|
|
@ -73,12 +78,33 @@ public partial class Main : Node2D
|
|||
}
|
||||
}
|
||||
|
||||
private void FadeIn(float duration)
|
||||
{
|
||||
_fadeOverlay.Color = new Color(0, 0, 0, 1);
|
||||
var tween = CreateTween();
|
||||
tween.TweenProperty(_fadeOverlay, "color:a", 0f, duration)
|
||||
.SetEase(Tween.EaseType.Out);
|
||||
}
|
||||
|
||||
private void FadeOut(float duration, Action onComplete)
|
||||
{
|
||||
_fadeOverlay.Color = new Color(0, 0, 0, 0);
|
||||
var tween = CreateTween();
|
||||
tween.TweenProperty(_fadeOverlay, "color:a", 1f, duration)
|
||||
.SetEase(Tween.EaseType.In);
|
||||
tween.TweenCallback(Callable.From(onComplete));
|
||||
}
|
||||
|
||||
private void BuildSceneTree()
|
||||
{
|
||||
// Camera
|
||||
_camera = new Camera2D { Enabled = true };
|
||||
AddChild(_camera);
|
||||
|
||||
// SFX
|
||||
var sfx = new SfxManager();
|
||||
AddChild(sfx);
|
||||
|
||||
// Board
|
||||
_boardView = new BoardView();
|
||||
AddChild(_boardView);
|
||||
|
|
@ -204,6 +230,15 @@ public partial class Main : Node2D
|
|||
_levelSelectScreen.SetAnchorsPreset(Control.LayoutPreset.FullRect);
|
||||
uiRoot.AddChild(_levelSelectScreen);
|
||||
|
||||
// --- Fade overlay (on top of everything) ---
|
||||
_fadeOverlay = new ColorRect
|
||||
{
|
||||
Color = new Color(0, 0, 0, 1),
|
||||
MouseFilter = Control.MouseFilterEnum.Ignore
|
||||
};
|
||||
_fadeOverlay.SetAnchorsPreset(Control.LayoutPreset.FullRect);
|
||||
uiRoot.AddChild(_fadeOverlay);
|
||||
|
||||
// Initialize animator
|
||||
_eventAnimator.Initialize(_boardView, _objectivePanel, _controlBar, _metricsOverlay);
|
||||
}
|
||||
|
|
@ -254,8 +289,15 @@ public partial class Main : Node2D
|
|||
|
||||
private void OnLevelSelected(int levelIndex)
|
||||
{
|
||||
SfxManager.Instance?.PlayClick();
|
||||
_currentLevelIndex = levelIndex;
|
||||
|
||||
// Fade out, load, fade in
|
||||
FadeOut(0.25f, () =>
|
||||
{
|
||||
LoadLevel(levelIndex);
|
||||
FadeIn(0.3f);
|
||||
});
|
||||
}
|
||||
|
||||
private void LoadLevel(int index)
|
||||
|
|
@ -370,17 +412,13 @@ public partial class Main : Node2D
|
|||
|
||||
private void CreatePieceVisual(PiecePlacedEvent placed)
|
||||
{
|
||||
SfxManager.Instance?.PlayPlace();
|
||||
|
||||
var pieceView = new PieceView();
|
||||
pieceView.Setup(placed.PieceId, placed.Kind, placed.Start, placed.End, _boardView);
|
||||
_boardView.AddChild(pieceView);
|
||||
|
||||
var color = placed.Kind switch
|
||||
{
|
||||
PieceKind.Rook => new Color("#4A7AB5"),
|
||||
PieceKind.Bishop => new Color("#B54A8E"),
|
||||
PieceKind.Knight => new Color("#B5824A"),
|
||||
_ => Colors.White
|
||||
};
|
||||
var color = PieceView.GetPieceColor(placed.Kind);
|
||||
|
||||
var trajectView = new TrajectView();
|
||||
trajectView.Setup(placed.PieceId,
|
||||
|
|
|
|||
|
|
@ -6,21 +6,25 @@ namespace Chessistics.Scripts.Pieces;
|
|||
|
||||
public partial class PieceView : Node2D
|
||||
{
|
||||
private Sprite2D _shadow = null!;
|
||||
private Sprite2D _sprite = null!;
|
||||
private ColorRect _cargoIndicator = null!;
|
||||
private Label _label = null!;
|
||||
private Tween? _cargoPulseTween;
|
||||
|
||||
public int PieceId { get; private set; }
|
||||
public PieceKind Kind { get; private set; }
|
||||
public Coords StartCell { get; private set; }
|
||||
public Coords EndCell { get; private set; }
|
||||
|
||||
private static readonly Color PawnColor = new("#7AB54A");
|
||||
private static readonly Color RookColor = new("#4A7AB5");
|
||||
private static readonly Color BishopColor = new("#B54A8E");
|
||||
private static readonly Color KnightColor = new("#B5824A");
|
||||
private static readonly Color WoodCargoColor = new("#8B6914");
|
||||
private static readonly Color StoneCargoColor = new("#808080");
|
||||
// Muted, earthy palette — teal, sienna, gold tones
|
||||
private static readonly Color PawnColor = new("#5A8C6B"); // sage green
|
||||
private static readonly Color RookColor = new("#3D6B8E"); // deep teal
|
||||
private static readonly Color BishopColor = new("#8E5A6B"); // dusty rose
|
||||
private static readonly Color KnightColor = new("#8E7A3D"); // burnt sienna
|
||||
private static readonly Color WoodCargoColor = new("#A67C32");
|
||||
private static readonly Color StoneCargoColor = new("#7A7A7A");
|
||||
private static readonly Color ShadowColor = new Color(0, 0, 0, 0.18f);
|
||||
|
||||
public void Setup(int pieceId, PieceKind kind, Coords startCell, Coords endCell, BoardView boardView)
|
||||
{
|
||||
|
|
@ -31,26 +35,36 @@ public partial class PieceView : Node2D
|
|||
|
||||
Position = boardView.CoordsToPixel(startCell);
|
||||
|
||||
var color = kind switch
|
||||
{
|
||||
PieceKind.Pawn => PawnColor,
|
||||
PieceKind.Rook => RookColor,
|
||||
PieceKind.Bishop => BishopColor,
|
||||
PieceKind.Knight => KnightColor,
|
||||
_ => Colors.White
|
||||
};
|
||||
var color = GetPieceColor(kind);
|
||||
|
||||
// Piece body (circle)
|
||||
_sprite = new Sprite2D();
|
||||
var texture = new GradientTexture2D
|
||||
// Shadow (slightly offset, rendered first)
|
||||
_shadow = new Sprite2D();
|
||||
var shadowTex = new GradientTexture2D
|
||||
{
|
||||
Width = 48,
|
||||
Height = 48,
|
||||
Width = 44, Height = 44,
|
||||
Fill = GradientTexture2D.FillEnum.Radial,
|
||||
Gradient = new Gradient()
|
||||
};
|
||||
texture.Gradient.SetColor(0, color);
|
||||
texture.Gradient.SetColor(1, color.Darkened(0.3f));
|
||||
shadowTex.Gradient.SetColor(0, ShadowColor);
|
||||
shadowTex.Gradient.SetColor(1, new Color(0, 0, 0, 0));
|
||||
_shadow.Texture = shadowTex;
|
||||
_shadow.Position = new Vector2(3, 5); // subtle offset down-right
|
||||
AddChild(_shadow);
|
||||
|
||||
// Piece body (circle with inner glow)
|
||||
_sprite = new Sprite2D();
|
||||
var texture = new GradientTexture2D
|
||||
{
|
||||
Width = 48, Height = 48,
|
||||
Fill = GradientTexture2D.FillEnum.Radial,
|
||||
Gradient = new Gradient()
|
||||
};
|
||||
texture.Gradient.Colors = [
|
||||
color.Lightened(0.15f), // bright center
|
||||
color, // main color
|
||||
color.Darkened(0.25f) // dark rim
|
||||
];
|
||||
texture.Gradient.Offsets = [0f, 0.5f, 1f];
|
||||
_sprite.Texture = texture;
|
||||
AddChild(_sprite);
|
||||
|
||||
|
|
@ -71,25 +85,46 @@ public partial class PieceView : Node2D
|
|||
MouseFilter = Control.MouseFilterEnum.Ignore
|
||||
};
|
||||
_label.AddThemeFontSizeOverride("font_size", 16);
|
||||
_label.AddThemeColorOverride("font_color", Colors.White);
|
||||
_label.AddThemeColorOverride("font_color", new Color(1, 1, 1, 0.9f));
|
||||
AddChild(_label);
|
||||
|
||||
// Cargo indicator (hidden by default)
|
||||
_cargoIndicator = new ColorRect
|
||||
{
|
||||
Size = new Vector2(14, 14),
|
||||
Position = new Vector2(-7, -30),
|
||||
Size = new Vector2(12, 12),
|
||||
Position = new Vector2(-6, -28),
|
||||
Visible = false,
|
||||
MouseFilter = Control.MouseFilterEnum.Ignore
|
||||
};
|
||||
AddChild(_cargoIndicator);
|
||||
|
||||
// Entrance animation: scale bounce
|
||||
Scale = Vector2.Zero;
|
||||
var entranceTween = CreateTween();
|
||||
entranceTween.TweenProperty(this, "scale", new Vector2(1.15f, 1.15f), 0.12f)
|
||||
.SetEase(Tween.EaseType.Out).SetTrans(Tween.TransitionType.Back);
|
||||
entranceTween.TweenProperty(this, "scale", Vector2.One, 0.08f)
|
||||
.SetEase(Tween.EaseType.InOut);
|
||||
}
|
||||
|
||||
public static Color GetPieceColor(PieceKind kind) => kind switch
|
||||
{
|
||||
PieceKind.Pawn => PawnColor,
|
||||
PieceKind.Rook => RookColor,
|
||||
PieceKind.Bishop => BishopColor,
|
||||
PieceKind.Knight => KnightColor,
|
||||
_ => Colors.White
|
||||
};
|
||||
|
||||
public void SetCargo(CargoType? cargo)
|
||||
{
|
||||
_cargoPulseTween?.Kill();
|
||||
_cargoPulseTween = null;
|
||||
|
||||
if (cargo == null)
|
||||
{
|
||||
_cargoIndicator.Visible = false;
|
||||
_cargoIndicator.Scale = Vector2.One;
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -100,6 +135,16 @@ public partial class PieceView : Node2D
|
|||
CargoType.Stone => StoneCargoColor,
|
||||
_ => Colors.White
|
||||
};
|
||||
|
||||
// Cargo pulse: gentle breathing
|
||||
_cargoPulseTween = CreateTween();
|
||||
_cargoPulseTween.SetLoops();
|
||||
_cargoPulseTween.TweenProperty(_cargoIndicator, "scale",
|
||||
new Vector2(1.25f, 1.25f), 0.5f)
|
||||
.SetEase(Tween.EaseType.InOut).SetTrans(Tween.TransitionType.Sine);
|
||||
_cargoPulseTween.TweenProperty(_cargoIndicator, "scale",
|
||||
Vector2.One, 0.5f)
|
||||
.SetEase(Tween.EaseType.InOut).SetTrans(Tween.TransitionType.Sine);
|
||||
}
|
||||
|
||||
public void AnimateMoveTo(Vector2 target, float duration = 0.3f)
|
||||
|
|
@ -107,7 +152,6 @@ public partial class PieceView : Node2D
|
|||
var tween = CreateTween();
|
||||
if (Kind == PieceKind.Knight)
|
||||
{
|
||||
// Arc animation for knight
|
||||
var mid = (Position + target) / 2 + new Vector2(0, -30);
|
||||
tween.TweenMethod(Callable.From<float>(t =>
|
||||
{
|
||||
|
|
@ -119,7 +163,8 @@ public partial class PieceView : Node2D
|
|||
}
|
||||
else
|
||||
{
|
||||
tween.TweenProperty(this, "position", target, duration);
|
||||
tween.TweenProperty(this, "position", target, duration)
|
||||
.SetEase(Tween.EaseType.InOut).SetTrans(Tween.TransitionType.Sine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,15 +5,34 @@ namespace Chessistics.Scripts.Pieces;
|
|||
public partial class TrajectView : Line2D
|
||||
{
|
||||
public int PieceId { get; private set; }
|
||||
private Polygon2D? _arrow;
|
||||
|
||||
public void Setup(int pieceId, Vector2 from, Vector2 to, Color color)
|
||||
{
|
||||
PieceId = pieceId;
|
||||
Width = 3f;
|
||||
DefaultColor = new Color(color, 0.5f);
|
||||
Width = 2.5f;
|
||||
DefaultColor = new Color(color, 0.35f);
|
||||
Antialiased = true;
|
||||
ClearPoints();
|
||||
AddPoint(from);
|
||||
AddPoint(to);
|
||||
ZIndex = -1;
|
||||
|
||||
// Arrowhead at the end point
|
||||
var dir = (to - from).Normalized();
|
||||
var perp = new Vector2(-dir.Y, dir.X);
|
||||
float arrowSize = 8f;
|
||||
var tip = to - dir * 4f; // slightly inset from end
|
||||
var baseL = tip - dir * arrowSize + perp * arrowSize * 0.5f;
|
||||
var baseR = tip - dir * arrowSize - perp * arrowSize * 0.5f;
|
||||
|
||||
_arrow = new Polygon2D
|
||||
{
|
||||
Polygon = [tip - Position, baseL - Position, baseR - Position],
|
||||
Color = new Color(color, 0.4f),
|
||||
Position = Vector2.Zero
|
||||
};
|
||||
// Position relative to parent, not this Line2D
|
||||
AddChild(_arrow);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,14 +23,14 @@ public partial class EventAnimator : Node
|
|||
private bool _animating;
|
||||
public bool IsAnimating => _animating;
|
||||
|
||||
private static readonly Color WoodCargoColor = new("#8B6914");
|
||||
private static readonly Color StoneCargoColor = new("#808080");
|
||||
private static readonly Color WoodCargoColor = new("#A67C32");
|
||||
private static readonly Color StoneCargoColor = new("#7A7A7A");
|
||||
|
||||
private const float ProduceDuration = 0.3f;
|
||||
private const float TransferDuration = 0.25f;
|
||||
private const float MoveDuration = 0.3f;
|
||||
private const float KnightMoveDuration = 0.4f;
|
||||
private const float DestroyDuration = 0.3f;
|
||||
private const float ProduceDuration = 0.35f;
|
||||
private const float TransferDuration = 0.28f;
|
||||
private const float MoveDuration = 0.32f;
|
||||
private const float KnightMoveDuration = 0.42f;
|
||||
private const float DestroyDuration = 0.45f;
|
||||
|
||||
[Signal]
|
||||
public delegate void TurnAnimationCompletedEventHandler();
|
||||
|
|
@ -107,6 +107,8 @@ public partial class EventAnimator : Node
|
|||
FlushPhases(tween, produceEvents, transferEvents, moveEvents, collisionEvents);
|
||||
tween.TweenCallback(Callable.From(() =>
|
||||
{
|
||||
SfxManager.Instance?.PlayVictory();
|
||||
SpawnConfetti();
|
||||
_metricsOverlay.ShowMetrics(victory.Metrics);
|
||||
EmitSignal(SignalName.VictoryReached);
|
||||
}));
|
||||
|
|
@ -137,45 +139,50 @@ public partial class EventAnimator : Node
|
|||
List<PieceMovedEvent> moveEvents,
|
||||
List<PieceDestroyedEvent> collisionEvents)
|
||||
{
|
||||
// Phase 1: Produce — flash production cells
|
||||
// Phase 1: Produce — warm golden flash + particle burst
|
||||
if (produceEvents.Count > 0)
|
||||
{
|
||||
var captured = produceEvents.ToList();
|
||||
tween.TweenCallback(Callable.From(() =>
|
||||
{
|
||||
foreach (var evt in produceEvents.ToList())
|
||||
SfxManager.Instance?.PlayProduce();
|
||||
foreach (var evt in captured)
|
||||
{
|
||||
var cell = _boardView.GetCellView(evt.ProductionCell);
|
||||
cell?.FlashProduce(ProduceDuration);
|
||||
SpawnProduceParticles(evt.ProductionCell, evt.Type);
|
||||
}
|
||||
}));
|
||||
tween.TweenInterval(ProduceDuration);
|
||||
produceEvents.Clear();
|
||||
}
|
||||
|
||||
// Phase 2: Transfers — animate cargo sliding from giver to receiver
|
||||
// Phase 2: Transfers — cargo slides with trail particles
|
||||
if (transferEvents.Count > 0)
|
||||
{
|
||||
// Capture the events list before clearing
|
||||
var eventsToAnimate = transferEvents.ToList();
|
||||
|
||||
// Step 1: remove cargo from givers + spawn sliding cargo sprites
|
||||
tween.TweenCallback(Callable.From(() =>
|
||||
{
|
||||
bool hasDelivery = false;
|
||||
foreach (var evt in eventsToAnimate)
|
||||
{
|
||||
if (evt is CargoTransferredEvent transfer)
|
||||
{
|
||||
// Remove cargo indicator from giver
|
||||
if (transfer.GivingPieceId != null && _pieceViews.ContainsKey(transfer.GivingPieceId.Value))
|
||||
_pieceViews[transfer.GivingPieceId.Value].SetCargo(null);
|
||||
|
||||
// Create sliding cargo sprite
|
||||
SpawnCargoSlide(transfer);
|
||||
|
||||
if (transfer.ReceivingPieceId == null) hasDelivery = true;
|
||||
}
|
||||
}
|
||||
if (hasDelivery)
|
||||
SfxManager.Instance?.PlayDeliver();
|
||||
else
|
||||
SfxManager.Instance?.PlayTransfer();
|
||||
}));
|
||||
|
||||
// Step 2: wait for slide, then show cargo on receivers + update demand progress
|
||||
tween.TweenInterval(TransferDuration);
|
||||
tween.TweenCallback(Callable.From(() =>
|
||||
{
|
||||
|
|
@ -196,9 +203,10 @@ public partial class EventAnimator : Node
|
|||
transferEvents.Clear();
|
||||
}
|
||||
|
||||
// Phase 3: Movement — all pieces move simultaneously
|
||||
// Phase 3: Movement — simultaneous, with sfx
|
||||
if (moveEvents.Count > 0)
|
||||
{
|
||||
tween.TweenCallback(Callable.From(() => SfxManager.Instance?.PlayMove()));
|
||||
tween.SetParallel(true);
|
||||
foreach (var moved in moveEvents)
|
||||
{
|
||||
|
|
@ -206,74 +214,218 @@ public partial class EventAnimator : Node
|
|||
{
|
||||
var target = _boardView.CoordsToPixel(moved.To);
|
||||
float duration = pv.Kind == PieceKind.Knight ? KnightMoveDuration : MoveDuration;
|
||||
tween.TweenProperty(pv, "position", target, duration);
|
||||
tween.TweenProperty(pv, "position", target, duration)
|
||||
.SetEase(Tween.EaseType.InOut).SetTrans(Tween.TransitionType.Sine);
|
||||
}
|
||||
}
|
||||
tween.SetParallel(false);
|
||||
moveEvents.Clear();
|
||||
}
|
||||
|
||||
// Phase 4: Collision/Destruction
|
||||
// Phase 4: Collision/Destruction — shrink + spin + particles
|
||||
if (collisionEvents.Count > 0)
|
||||
{
|
||||
tween.SetParallel(true);
|
||||
foreach (var destroyed in collisionEvents)
|
||||
{
|
||||
var pieceId = destroyed.PieceId;
|
||||
var captured = collisionEvents.ToList();
|
||||
tween.TweenCallback(Callable.From(() =>
|
||||
{
|
||||
FlashPiece(pieceId);
|
||||
UnregisterPiece(pieceId);
|
||||
}));
|
||||
SfxManager.Instance?.PlayDestroy();
|
||||
foreach (var destroyed in captured)
|
||||
{
|
||||
if (_pieceViews.TryGetValue(destroyed.PieceId, out var pv))
|
||||
{
|
||||
SpawnDestroyParticles(pv.Position);
|
||||
|
||||
var dt = pv.CreateTween();
|
||||
dt.SetParallel(true);
|
||||
dt.TweenProperty(pv, "scale", Vector2.Zero, DestroyDuration)
|
||||
.SetEase(Tween.EaseType.In).SetTrans(Tween.TransitionType.Back);
|
||||
dt.TweenProperty(pv, "rotation", Mathf.Pi * 2f, DestroyDuration);
|
||||
dt.TweenProperty(pv, "modulate:a", 0f, DestroyDuration);
|
||||
}
|
||||
tween.SetParallel(false);
|
||||
}
|
||||
}));
|
||||
tween.TweenInterval(DestroyDuration);
|
||||
tween.TweenCallback(Callable.From(() =>
|
||||
{
|
||||
foreach (var destroyed in captured)
|
||||
UnregisterPiece(destroyed.PieceId);
|
||||
}));
|
||||
collisionEvents.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a temporary colored square that slides from the giver to the receiver.
|
||||
/// </summary>
|
||||
// --- Visual Effects ---
|
||||
|
||||
private void SpawnCargoSlide(CargoTransferredEvent transfer)
|
||||
{
|
||||
var from = _boardView.CoordsToPixel(transfer.From);
|
||||
var to = _boardView.CoordsToPixel(transfer.To);
|
||||
var color = transfer.Type switch
|
||||
var color = GetCargoColor(transfer.Type);
|
||||
|
||||
var container = new Node2D { Position = from };
|
||||
_boardView.AddChild(container);
|
||||
|
||||
// Main cargo square
|
||||
var main = new ColorRect
|
||||
{
|
||||
Size = new Vector2(12, 12),
|
||||
Position = new Vector2(-6, -6),
|
||||
Color = color,
|
||||
MouseFilter = Control.MouseFilterEnum.Ignore
|
||||
};
|
||||
container.AddChild(main);
|
||||
|
||||
// Trail particles (2 smaller squares that follow with delay)
|
||||
for (int i = 1; i <= 2; i++)
|
||||
{
|
||||
float trailDelay = i * 0.04f;
|
||||
float trailSize = 12f - i * 3f;
|
||||
float trailAlpha = 0.6f - i * 0.2f;
|
||||
|
||||
var trail = new ColorRect
|
||||
{
|
||||
Size = new Vector2(trailSize, trailSize),
|
||||
Position = new Vector2(-trailSize / 2f, -trailSize / 2f),
|
||||
Color = new Color(color, trailAlpha),
|
||||
MouseFilter = Control.MouseFilterEnum.Ignore
|
||||
};
|
||||
var trailContainer = new Node2D { Position = from };
|
||||
trailContainer.AddChild(trail);
|
||||
_boardView.AddChild(trailContainer);
|
||||
|
||||
var trailTween = trailContainer.CreateTween();
|
||||
trailTween.TweenInterval(trailDelay);
|
||||
trailTween.TweenProperty(trailContainer, "position", to, TransferDuration - trailDelay)
|
||||
.SetEase(Tween.EaseType.Out).SetTrans(Tween.TransitionType.Back);
|
||||
trailTween.TweenCallback(Callable.From(() => trailContainer.QueueFree()));
|
||||
}
|
||||
|
||||
// Main slide with whip easing
|
||||
var slideTween = container.CreateTween();
|
||||
slideTween.TweenProperty(container, "position", to, TransferDuration)
|
||||
.SetEase(Tween.EaseType.Out).SetTrans(Tween.TransitionType.Back);
|
||||
slideTween.TweenCallback(Callable.From(() => container.QueueFree()));
|
||||
}
|
||||
|
||||
private void SpawnProduceParticles(Coords cell, CargoType type)
|
||||
{
|
||||
var center = _boardView.CoordsToPixel(cell);
|
||||
var color = GetCargoColor(type);
|
||||
var rng = new Random();
|
||||
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
float angle = i * Mathf.Pi * 2f / 6f + (float)rng.NextDouble() * 0.5f;
|
||||
float dist = 25f + (float)rng.NextDouble() * 15f;
|
||||
float size = 4f + (float)rng.NextDouble() * 4f;
|
||||
|
||||
var particle = new ColorRect
|
||||
{
|
||||
Size = new Vector2(size, size),
|
||||
Position = new Vector2(-size / 2f, -size / 2f),
|
||||
Color = new Color(color, 0.8f),
|
||||
MouseFilter = Control.MouseFilterEnum.Ignore
|
||||
};
|
||||
var pNode = new Node2D { Position = center };
|
||||
pNode.AddChild(particle);
|
||||
_boardView.AddChild(pNode);
|
||||
|
||||
var target = center + new Vector2(Mathf.Cos(angle), Mathf.Sin(angle)) * dist;
|
||||
var pt = pNode.CreateTween();
|
||||
pt.SetParallel(true);
|
||||
pt.TweenProperty(pNode, "position", target, ProduceDuration * 0.8f)
|
||||
.SetEase(Tween.EaseType.Out).SetTrans(Tween.TransitionType.Cubic);
|
||||
pt.TweenProperty(pNode, "modulate:a", 0f, ProduceDuration);
|
||||
pt.SetParallel(false);
|
||||
pt.TweenCallback(Callable.From(() => pNode.QueueFree()));
|
||||
}
|
||||
}
|
||||
|
||||
private void SpawnDestroyParticles(Vector2 position)
|
||||
{
|
||||
var rng = new Random();
|
||||
var red = new Color(0.9f, 0.25f, 0.2f);
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
float angle = (float)rng.NextDouble() * Mathf.Pi * 2f;
|
||||
float dist = 20f + (float)rng.NextDouble() * 30f;
|
||||
float size = 3f + (float)rng.NextDouble() * 5f;
|
||||
|
||||
var particle = new ColorRect
|
||||
{
|
||||
Size = new Vector2(size, size),
|
||||
Position = new Vector2(-size / 2f, -size / 2f),
|
||||
Color = new Color(red, 0.9f),
|
||||
MouseFilter = Control.MouseFilterEnum.Ignore
|
||||
};
|
||||
var pNode = new Node2D { Position = position };
|
||||
pNode.AddChild(particle);
|
||||
_boardView.AddChild(pNode);
|
||||
|
||||
var target = position + new Vector2(Mathf.Cos(angle), Mathf.Sin(angle)) * dist;
|
||||
var pt = pNode.CreateTween();
|
||||
pt.SetParallel(true);
|
||||
pt.TweenProperty(pNode, "position", target, DestroyDuration * 0.7f)
|
||||
.SetEase(Tween.EaseType.Out).SetTrans(Tween.TransitionType.Cubic);
|
||||
pt.TweenProperty(pNode, "modulate:a", 0f, DestroyDuration);
|
||||
pt.TweenProperty(pNode, "scale", Vector2.Zero, DestroyDuration)
|
||||
.SetEase(Tween.EaseType.In);
|
||||
pt.SetParallel(false);
|
||||
pt.TweenCallback(Callable.From(() => pNode.QueueFree()));
|
||||
}
|
||||
}
|
||||
|
||||
private void SpawnConfetti()
|
||||
{
|
||||
var rng = new Random();
|
||||
var viewport = GetViewport().GetVisibleRect().Size;
|
||||
Color[] confettiColors = [
|
||||
new("#FFD700"), new("#FF6B35"), new("#3D8E5A"),
|
||||
new("#4A7AB5"), new("#B54A8E"), new("#E8D5A8")
|
||||
];
|
||||
|
||||
for (int i = 0; i < 40; i++)
|
||||
{
|
||||
float x = (float)rng.NextDouble() * viewport.X;
|
||||
float startY = -20f;
|
||||
float endY = viewport.Y + 50f;
|
||||
float size = 4f + (float)rng.NextDouble() * 6f;
|
||||
float duration = 1.5f + (float)rng.NextDouble() * 1.5f;
|
||||
float delay = (float)rng.NextDouble() * 0.5f;
|
||||
float drift = ((float)rng.NextDouble() - 0.5f) * 80f;
|
||||
|
||||
var confetti = new ColorRect
|
||||
{
|
||||
Size = new Vector2(size, size * 0.5f),
|
||||
Color = confettiColors[rng.Next(confettiColors.Length)],
|
||||
MouseFilter = Control.MouseFilterEnum.Ignore
|
||||
};
|
||||
|
||||
// Confetti needs to be in screen space (CanvasLayer)
|
||||
var parent = GetTree().Root;
|
||||
var cNode = new Node2D { Position = new Vector2(x, startY) };
|
||||
cNode.AddChild(confetti);
|
||||
parent.AddChild(cNode);
|
||||
|
||||
var ct = cNode.CreateTween();
|
||||
ct.TweenInterval(delay);
|
||||
ct.SetParallel(true);
|
||||
ct.TweenProperty(cNode, "position", new Vector2(x + drift, endY), duration)
|
||||
.SetEase(Tween.EaseType.In).SetTrans(Tween.TransitionType.Sine);
|
||||
ct.TweenProperty(cNode, "rotation", (float)rng.NextDouble() * Mathf.Pi * 4f, duration);
|
||||
ct.SetParallel(false);
|
||||
ct.TweenCallback(Callable.From(() => cNode.QueueFree()));
|
||||
}
|
||||
}
|
||||
|
||||
private static Color GetCargoColor(CargoType type) => type switch
|
||||
{
|
||||
CargoType.Wood => WoodCargoColor,
|
||||
CargoType.Stone => StoneCargoColor,
|
||||
_ => Colors.White
|
||||
};
|
||||
|
||||
var sprite = new ColorRect
|
||||
{
|
||||
Size = new Vector2(14, 14),
|
||||
Position = new Vector2(-7, -7),
|
||||
Color = color,
|
||||
MouseFilter = Control.MouseFilterEnum.Ignore
|
||||
};
|
||||
|
||||
var container = new Node2D { Position = from };
|
||||
container.AddChild(sprite);
|
||||
_boardView.AddChild(container);
|
||||
|
||||
var slideTween = container.CreateTween();
|
||||
slideTween.TweenProperty(container, "position", to, TransferDuration)
|
||||
.SetEase(Tween.EaseType.InOut)
|
||||
.SetTrans(Tween.TransitionType.Cubic);
|
||||
slideTween.TweenCallback(Callable.From(() => container.QueueFree()));
|
||||
}
|
||||
|
||||
private void FlashPiece(int pieceId)
|
||||
{
|
||||
if (!_pieceViews.TryGetValue(pieceId, out var pv)) return;
|
||||
var tween = pv.CreateTween();
|
||||
tween.TweenProperty(pv, "modulate", new Color(1, 0.2f, 0.2f), 0.1f);
|
||||
tween.TweenProperty(pv, "modulate", Colors.White, 0.1f);
|
||||
tween.SetLoops(3);
|
||||
}
|
||||
|
||||
public void ResetPiecePositions(BoardSnapshot snapshot)
|
||||
{
|
||||
foreach (var ps in snapshot.Pieces)
|
||||
|
|
|
|||
160
Scripts/Presentation/SfxManager.cs
Normal file
160
Scripts/Presentation/SfxManager.cs
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
using Godot;
|
||||
using System;
|
||||
|
||||
namespace Chessistics.Scripts.Presentation;
|
||||
|
||||
/// <summary>
|
||||
/// Procedural sound effects via synthesized waveforms.
|
||||
/// No external audio files needed — everything is generated in code.
|
||||
/// </summary>
|
||||
public partial class SfxManager : Node
|
||||
{
|
||||
private const int SampleRate = 22050;
|
||||
private const float MasterVolume = 0.15f;
|
||||
|
||||
public static SfxManager? Instance { get; private set; }
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
// --- Public API ---
|
||||
|
||||
public void PlayPlace() => PlayTone(523.25f, 0.08f, vol: 0.5f); // C5 short blip
|
||||
public void PlayProduce() => PlayTone(130.81f, 0.12f, vol: 0.3f, wave: Wave.Triangle); // C3 warm
|
||||
public void PlayTransfer() => PlayNoise(0.12f, vol: 0.15f); // filtered swoosh
|
||||
public void PlayDeliver() => PlayChord([523.25f, 659.25f], 0.18f, vol: 0.4f); // C5+E5 ding
|
||||
public void PlayMove() => PlayNoise(0.04f, vol: 0.08f); // tiny whoosh
|
||||
public void PlayDestroy() => PlaySweep(262f, 65f, 0.18f, vol: 0.4f); // descending crunch
|
||||
public void PlayVictory() => PlayArpeggio([262f, 330f, 392f, 523f], 0.12f, vol: 0.35f); // C-E-G-C arp
|
||||
public void PlayClick() => PlayTone(880f, 0.02f, vol: 0.2f); // tiny tick
|
||||
|
||||
// --- Synthesis ---
|
||||
|
||||
private enum Wave { Sine, Triangle, Square }
|
||||
|
||||
private void PlayTone(float freq, float duration, float vol = 0.3f, Wave wave = Wave.Sine)
|
||||
{
|
||||
var samples = GenerateTone(freq, duration, vol, wave);
|
||||
PlaySamples(samples);
|
||||
}
|
||||
|
||||
private void PlayNoise(float duration, float vol = 0.2f)
|
||||
{
|
||||
var count = (int)(SampleRate * duration);
|
||||
var samples = new float[count];
|
||||
var rng = new Random();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
float t = (float)i / count;
|
||||
float envelope = Envelope(t);
|
||||
samples[i] = (float)(rng.NextDouble() * 2 - 1) * vol * envelope * MasterVolume;
|
||||
}
|
||||
// Simple low-pass: average with previous sample
|
||||
for (int i = count - 1; i > 0; i--)
|
||||
samples[i] = (samples[i] + samples[i - 1]) * 0.5f;
|
||||
PlaySamples(samples);
|
||||
}
|
||||
|
||||
private void PlayChord(float[] freqs, float duration, float vol = 0.3f)
|
||||
{
|
||||
var count = (int)(SampleRate * duration);
|
||||
var samples = new float[count];
|
||||
foreach (var freq in freqs)
|
||||
{
|
||||
var tone = GenerateTone(freq, duration, vol / freqs.Length, Wave.Sine);
|
||||
for (int i = 0; i < count; i++)
|
||||
samples[i] += tone[i];
|
||||
}
|
||||
PlaySamples(samples);
|
||||
}
|
||||
|
||||
private void PlaySweep(float startFreq, float endFreq, float duration, float vol = 0.3f)
|
||||
{
|
||||
var count = (int)(SampleRate * duration);
|
||||
var samples = new float[count];
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
float t = (float)i / count;
|
||||
float freq = Mathf.Lerp(startFreq, endFreq, t);
|
||||
float envelope = Envelope(t);
|
||||
float phase = 2f * Mathf.Pi * freq * i / SampleRate;
|
||||
samples[i] = Mathf.Sin(phase) * vol * envelope * MasterVolume;
|
||||
}
|
||||
PlaySamples(samples);
|
||||
}
|
||||
|
||||
private void PlayArpeggio(float[] notes, float noteLength, float vol = 0.3f)
|
||||
{
|
||||
float totalDuration = noteLength * notes.Length;
|
||||
var totalCount = (int)(SampleRate * totalDuration);
|
||||
var samples = new float[totalCount];
|
||||
var noteCount = (int)(SampleRate * noteLength);
|
||||
|
||||
for (int n = 0; n < notes.Length; n++)
|
||||
{
|
||||
int offset = n * noteCount;
|
||||
var tone = GenerateTone(notes[n], noteLength, vol, Wave.Sine);
|
||||
for (int i = 0; i < tone.Length && offset + i < totalCount; i++)
|
||||
samples[offset + i] += tone[i];
|
||||
}
|
||||
PlaySamples(samples);
|
||||
}
|
||||
|
||||
private float[] GenerateTone(float freq, float duration, float vol, Wave wave)
|
||||
{
|
||||
var count = (int)(SampleRate * duration);
|
||||
var samples = new float[count];
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
float t = (float)i / count;
|
||||
float phase = 2f * Mathf.Pi * freq * i / SampleRate;
|
||||
float value = wave switch
|
||||
{
|
||||
Wave.Triangle => 2f * Mathf.Abs(2f * ((freq * i / SampleRate) % 1f) - 1f) - 1f,
|
||||
Wave.Square => Mathf.Sin(phase) >= 0 ? 1f : -1f,
|
||||
_ => Mathf.Sin(phase)
|
||||
};
|
||||
float envelope = Envelope(t);
|
||||
samples[i] = value * vol * envelope * MasterVolume;
|
||||
}
|
||||
return samples;
|
||||
}
|
||||
|
||||
/// <summary>Simple ADSR-ish envelope: quick attack, sustain, smooth release.</summary>
|
||||
private static float Envelope(float t)
|
||||
{
|
||||
if (t < 0.05f) return t / 0.05f; // attack
|
||||
if (t < 0.3f) return 1f; // sustain
|
||||
return 1f - (t - 0.3f) / 0.7f; // release
|
||||
}
|
||||
|
||||
private void PlaySamples(float[] samples)
|
||||
{
|
||||
var stream = new AudioStreamWav
|
||||
{
|
||||
Format = AudioStreamWav.FormatEnum.Format16Bits,
|
||||
MixRate = SampleRate,
|
||||
Stereo = false,
|
||||
Data = FloatsToWav16(samples)
|
||||
};
|
||||
|
||||
var player = new AudioStreamPlayer { Stream = stream, VolumeDb = -6f };
|
||||
AddChild(player);
|
||||
player.Finished += () => player.QueueFree();
|
||||
player.Play();
|
||||
}
|
||||
|
||||
private static byte[] FloatsToWav16(float[] samples)
|
||||
{
|
||||
var bytes = new byte[samples.Length * 2];
|
||||
for (int i = 0; i < samples.Length; i++)
|
||||
{
|
||||
short val = (short)(Mathf.Clamp(samples[i], -1f, 1f) * 32767);
|
||||
bytes[i * 2] = (byte)(val & 0xFF);
|
||||
bytes[i * 2 + 1] = (byte)((val >> 8) & 0xFF);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
|
|
@ -24,38 +24,92 @@ public partial class ControlBar : HBoxContainer
|
|||
private OptionButton _speedSelect = null!;
|
||||
private Label _turnLabel = null!;
|
||||
|
||||
private static readonly Color BtnBg = new("#2A2A2E");
|
||||
private static readonly Color BtnHover = new("#3A3A40");
|
||||
private static readonly Color BtnPressed = new("#1A1A1E");
|
||||
private static readonly Color BtnDisabled = new("#1E1E20");
|
||||
private static readonly Color BtnBorder = new("#444448");
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
_playButton = new Button { Text = "▶ PLAY" };
|
||||
AddThemeConstantOverride("separation", 8);
|
||||
|
||||
_playButton = CreateStyledButton("PLAY");
|
||||
_playButton.Pressed += () => EmitSignal(SignalName.PlayPressed);
|
||||
AddChild(_playButton);
|
||||
|
||||
_pauseButton = new Button { Text = "⏸ PAUSE" };
|
||||
_pauseButton = CreateStyledButton("PAUSE");
|
||||
_pauseButton.Pressed += () => EmitSignal(SignalName.PausePressed);
|
||||
AddChild(_pauseButton);
|
||||
|
||||
_stepButton = new Button { Text = "⏭ STEP" };
|
||||
_stepButton = CreateStyledButton("STEP");
|
||||
_stepButton.Pressed += () => EmitSignal(SignalName.StepPressed);
|
||||
AddChild(_stepButton);
|
||||
|
||||
_stopButton = new Button { Text = "⏹ STOP" };
|
||||
_stopButton = CreateStyledButton("STOP");
|
||||
_stopButton.Pressed += () => EmitSignal(SignalName.StopPressed);
|
||||
AddChild(_stopButton);
|
||||
|
||||
_speedSelect = new OptionButton();
|
||||
// Spacer
|
||||
AddChild(new Control { CustomMinimumSize = new Vector2(12, 0) });
|
||||
|
||||
_speedSelect = new OptionButton { CustomMinimumSize = new Vector2(60, 30) };
|
||||
_speedSelect.AddItem("x1", 0);
|
||||
_speedSelect.AddItem("x2", 1);
|
||||
_speedSelect.AddItem("x4", 2);
|
||||
_speedSelect.ItemSelected += OnSpeedSelected;
|
||||
AddChild(_speedSelect);
|
||||
|
||||
// Spacer
|
||||
AddChild(new Control { SizeFlagsHorizontal = SizeFlags.ExpandFill });
|
||||
|
||||
_turnLabel = new Label { Text = "Coup: --" };
|
||||
_turnLabel.AddThemeFontSizeOverride("font_size", 14);
|
||||
_turnLabel.AddThemeFontSizeOverride("font_size", 13);
|
||||
_turnLabel.AddThemeColorOverride("font_color", new Color("#999999"));
|
||||
AddChild(_turnLabel);
|
||||
|
||||
UpdateForPhase(SimPhase.Edit);
|
||||
}
|
||||
|
||||
private static Button CreateStyledButton(string text)
|
||||
{
|
||||
var btn = new Button
|
||||
{
|
||||
Text = text,
|
||||
CustomMinimumSize = new Vector2(70, 30)
|
||||
};
|
||||
btn.AddThemeFontSizeOverride("font_size", 11);
|
||||
|
||||
var normal = MakeStyle(BtnBg);
|
||||
var hover = MakeStyle(BtnHover);
|
||||
var pressed = MakeStyle(BtnPressed);
|
||||
var disabled = MakeStyle(BtnDisabled);
|
||||
disabled.BorderColor = new Color("#2A2A2E");
|
||||
|
||||
btn.AddThemeStyleboxOverride("normal", normal);
|
||||
btn.AddThemeStyleboxOverride("hover", hover);
|
||||
btn.AddThemeStyleboxOverride("pressed", pressed);
|
||||
btn.AddThemeStyleboxOverride("disabled", disabled);
|
||||
btn.AddThemeColorOverride("font_disabled_color", new Color("#555555"));
|
||||
|
||||
return btn;
|
||||
}
|
||||
|
||||
private static StyleBoxFlat MakeStyle(Color bg)
|
||||
{
|
||||
return new StyleBoxFlat
|
||||
{
|
||||
BgColor = bg,
|
||||
BorderColor = BtnBorder,
|
||||
BorderWidthBottom = 1, BorderWidthTop = 1,
|
||||
BorderWidthLeft = 1, BorderWidthRight = 1,
|
||||
CornerRadiusTopLeft = 4, CornerRadiusTopRight = 4,
|
||||
CornerRadiusBottomLeft = 4, CornerRadiusBottomRight = 4,
|
||||
ContentMarginLeft = 10, ContentMarginRight = 10,
|
||||
ContentMarginTop = 4, ContentMarginBottom = 4
|
||||
};
|
||||
}
|
||||
|
||||
private void OnSpeedSelected(long index)
|
||||
{
|
||||
float speed = index switch
|
||||
|
|
|
|||
|
|
@ -10,46 +10,62 @@ public partial class MetricsOverlay : PanelContainer
|
|||
[Signal]
|
||||
public delegate void RetryPressedEventHandler();
|
||||
|
||||
private Label _metricsLabel = null!;
|
||||
private Label _titleLabel = null!;
|
||||
private Label _piecesLabel = null!;
|
||||
private Label _turnsLabel = null!;
|
||||
private Label _cellsLabel = null!;
|
||||
private HBoxContainer _buttons = null!;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
var vbox = new VBoxContainer();
|
||||
vbox.SetAnchorsPreset(LayoutPreset.Center);
|
||||
var style = new StyleBoxFlat
|
||||
{
|
||||
BgColor = new Color(0.1f, 0.1f, 0.12f, 0.95f),
|
||||
BorderColor = new Color("#B8942A"),
|
||||
BorderWidthBottom = 2, BorderWidthTop = 2,
|
||||
BorderWidthLeft = 2, BorderWidthRight = 2,
|
||||
CornerRadiusTopLeft = 12, CornerRadiusTopRight = 12,
|
||||
CornerRadiusBottomLeft = 12, CornerRadiusBottomRight = 12,
|
||||
ContentMarginLeft = 32, ContentMarginRight = 32,
|
||||
ContentMarginTop = 28, ContentMarginBottom = 28
|
||||
};
|
||||
AddThemeStyleboxOverride("panel", style);
|
||||
|
||||
var title = new Label
|
||||
var vbox = new VBoxContainer();
|
||||
vbox.AddThemeConstantOverride("separation", 8);
|
||||
|
||||
_titleLabel = new Label
|
||||
{
|
||||
Text = "VICTOIRE !",
|
||||
HorizontalAlignment = HorizontalAlignment.Center
|
||||
};
|
||||
title.AddThemeFontSizeOverride("font_size", 24);
|
||||
title.AddThemeColorOverride("font_color", new Color("#FFD700"));
|
||||
vbox.AddChild(title);
|
||||
_titleLabel.AddThemeFontSizeOverride("font_size", 26);
|
||||
_titleLabel.AddThemeColorOverride("font_color", new Color("#FFD700"));
|
||||
vbox.AddChild(_titleLabel);
|
||||
|
||||
vbox.AddChild(new HSeparator());
|
||||
|
||||
_metricsLabel = new Label
|
||||
{
|
||||
Text = "",
|
||||
HorizontalAlignment = HorizontalAlignment.Center
|
||||
};
|
||||
_metricsLabel.AddThemeFontSizeOverride("font_size", 14);
|
||||
vbox.AddChild(_metricsLabel);
|
||||
_piecesLabel = CreateMetricLabel();
|
||||
vbox.AddChild(_piecesLabel);
|
||||
_turnsLabel = CreateMetricLabel();
|
||||
vbox.AddChild(_turnsLabel);
|
||||
_cellsLabel = CreateMetricLabel();
|
||||
vbox.AddChild(_cellsLabel);
|
||||
|
||||
vbox.AddChild(new HSeparator());
|
||||
|
||||
var buttons = new HBoxContainer();
|
||||
buttons.Alignment = BoxContainer.AlignmentMode.Center;
|
||||
_buttons = new HBoxContainer { Alignment = BoxContainer.AlignmentMode.Center };
|
||||
_buttons.AddThemeConstantOverride("separation", 16);
|
||||
|
||||
var retryBtn = new Button { Text = "Rejouer" };
|
||||
var retryBtn = CreateStyledButton("Rejouer");
|
||||
retryBtn.Pressed += () => EmitSignal(SignalName.RetryPressed);
|
||||
buttons.AddChild(retryBtn);
|
||||
_buttons.AddChild(retryBtn);
|
||||
|
||||
var nextBtn = new Button { Text = "Niveau suivant" };
|
||||
var nextBtn = CreateStyledButton("Niveau suivant");
|
||||
nextBtn.Pressed += () => EmitSignal(SignalName.NextLevelPressed);
|
||||
buttons.AddChild(nextBtn);
|
||||
_buttons.AddChild(nextBtn);
|
||||
|
||||
vbox.AddChild(buttons);
|
||||
vbox.AddChild(_buttons);
|
||||
AddChild(vbox);
|
||||
|
||||
Visible = false;
|
||||
|
|
@ -57,11 +73,87 @@ public partial class MetricsOverlay : PanelContainer
|
|||
|
||||
public void ShowMetrics(Metrics metrics)
|
||||
{
|
||||
_metricsLabel.Text = $"Pieces utilisees: {metrics.PiecesUsed}\n" +
|
||||
$"Coups: {metrics.TurnsTaken}\n" +
|
||||
$"Cases occupees: {metrics.CellsOccupied}";
|
||||
_piecesLabel.Text = $"Pieces utilisees: {metrics.PiecesUsed}";
|
||||
_turnsLabel.Text = $"Coups: {metrics.TurnsTaken}";
|
||||
_cellsLabel.Text = $"Cases occupees: {metrics.CellsOccupied}";
|
||||
|
||||
// Start invisible, fade + scale in
|
||||
Modulate = new Color(1, 1, 1, 0);
|
||||
Scale = new Vector2(0.85f, 0.85f);
|
||||
PivotOffset = Size / 2f;
|
||||
Visible = true;
|
||||
|
||||
// Hide metrics initially, reveal sequentially
|
||||
_piecesLabel.Modulate = new Color(1, 1, 1, 0);
|
||||
_turnsLabel.Modulate = new Color(1, 1, 1, 0);
|
||||
_cellsLabel.Modulate = new Color(1, 1, 1, 0);
|
||||
_buttons.Modulate = new Color(1, 1, 1, 0);
|
||||
|
||||
var tween = CreateTween();
|
||||
// Panel fade in
|
||||
tween.SetParallel(true);
|
||||
tween.TweenProperty(this, "modulate:a", 1f, 0.3f)
|
||||
.SetEase(Tween.EaseType.Out);
|
||||
tween.TweenProperty(this, "scale", Vector2.One, 0.35f)
|
||||
.SetEase(Tween.EaseType.Out).SetTrans(Tween.TransitionType.Back);
|
||||
tween.SetParallel(false);
|
||||
|
||||
// Sequential metric reveals
|
||||
tween.TweenInterval(0.15f);
|
||||
tween.TweenProperty(_piecesLabel, "modulate:a", 1f, 0.2f);
|
||||
tween.TweenInterval(0.1f);
|
||||
tween.TweenProperty(_turnsLabel, "modulate:a", 1f, 0.2f);
|
||||
tween.TweenInterval(0.1f);
|
||||
tween.TweenProperty(_cellsLabel, "modulate:a", 1f, 0.2f);
|
||||
tween.TweenInterval(0.15f);
|
||||
tween.TweenProperty(_buttons, "modulate:a", 1f, 0.2f);
|
||||
}
|
||||
|
||||
public new void Hide() => Visible = false;
|
||||
public new void Hide()
|
||||
{
|
||||
Visible = false;
|
||||
}
|
||||
|
||||
private static Label CreateMetricLabel()
|
||||
{
|
||||
var label = new Label
|
||||
{
|
||||
Text = "",
|
||||
HorizontalAlignment = HorizontalAlignment.Center
|
||||
};
|
||||
label.AddThemeFontSizeOverride("font_size", 14);
|
||||
label.AddThemeColorOverride("font_color", new Color("#CCCCCC"));
|
||||
return label;
|
||||
}
|
||||
|
||||
private static Button CreateStyledButton(string text)
|
||||
{
|
||||
var btn = new Button
|
||||
{
|
||||
Text = text,
|
||||
CustomMinimumSize = new Vector2(130, 36)
|
||||
};
|
||||
|
||||
var normal = new StyleBoxFlat
|
||||
{
|
||||
BgColor = new Color("#3D6B8E"),
|
||||
CornerRadiusTopLeft = 6, CornerRadiusTopRight = 6,
|
||||
CornerRadiusBottomLeft = 6, CornerRadiusBottomRight = 6,
|
||||
ContentMarginLeft = 16, ContentMarginRight = 16,
|
||||
ContentMarginTop = 6, ContentMarginBottom = 6
|
||||
};
|
||||
var hover = new StyleBoxFlat
|
||||
{
|
||||
BgColor = new Color("#4A8EBF"),
|
||||
CornerRadiusTopLeft = 6, CornerRadiusTopRight = 6,
|
||||
CornerRadiusBottomLeft = 6, CornerRadiusBottomRight = 6,
|
||||
ContentMarginLeft = 16, ContentMarginRight = 16,
|
||||
ContentMarginTop = 6, ContentMarginBottom = 6
|
||||
};
|
||||
btn.AddThemeStyleboxOverride("normal", normal);
|
||||
btn.AddThemeStyleboxOverride("hover", hover);
|
||||
btn.AddThemeFontSizeOverride("font_size", 13);
|
||||
|
||||
return btn;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ namespace Chessistics.Scripts.UI;
|
|||
|
||||
public partial class ObjectivePanel : VBoxContainer
|
||||
{
|
||||
private readonly Dictionary<Coords, (Label label, ProgressBar bar)> _entries = new();
|
||||
private readonly Dictionary<Coords, (Label label, ProgressBar bar, Label deadline)> _entries = new();
|
||||
|
||||
public void Setup(IReadOnlyList<DemandDef> demands)
|
||||
{
|
||||
|
|
@ -16,7 +16,7 @@ public partial class ObjectivePanel : VBoxContainer
|
|||
|
||||
var title = new Label { Text = "OBJECTIFS" };
|
||||
title.AddThemeFontSizeOverride("font_size", 16);
|
||||
title.AddThemeColorOverride("font_color", new Color("#FFD700"));
|
||||
title.AddThemeColorOverride("font_color", new Color("#B8942A")); // aged gold
|
||||
AddChild(title);
|
||||
|
||||
AddChild(new HSeparator());
|
||||
|
|
@ -24,9 +24,11 @@ public partial class ObjectivePanel : VBoxContainer
|
|||
foreach (var demand in demands)
|
||||
{
|
||||
var vbox = new VBoxContainer();
|
||||
vbox.AddThemeConstantOverride("separation", 2);
|
||||
|
||||
var label = new Label { Text = $"{demand.Name}: 0/{demand.Amount} {demand.Cargo}" };
|
||||
label.AddThemeFontSizeOverride("font_size", 12);
|
||||
label.AddThemeColorOverride("font_color", new Color("#CCCCCC"));
|
||||
vbox.AddChild(label);
|
||||
|
||||
var bar = new ProgressBar
|
||||
|
|
@ -34,18 +36,34 @@ public partial class ObjectivePanel : VBoxContainer
|
|||
MinValue = 0,
|
||||
MaxValue = demand.Amount,
|
||||
Value = 0,
|
||||
CustomMinimumSize = new Vector2(180, 16),
|
||||
CustomMinimumSize = new Vector2(180, 14),
|
||||
ShowPercentage = false
|
||||
};
|
||||
|
||||
// Style the progress bar
|
||||
var bgStyle = new StyleBoxFlat
|
||||
{
|
||||
BgColor = new Color(0.2f, 0.2f, 0.22f),
|
||||
CornerRadiusTopLeft = 3, CornerRadiusTopRight = 3,
|
||||
CornerRadiusBottomLeft = 3, CornerRadiusBottomRight = 3
|
||||
};
|
||||
var fillStyle = new StyleBoxFlat
|
||||
{
|
||||
BgColor = new Color("#3D6B8E"), // teal fill
|
||||
CornerRadiusTopLeft = 3, CornerRadiusTopRight = 3,
|
||||
CornerRadiusBottomLeft = 3, CornerRadiusBottomRight = 3
|
||||
};
|
||||
bar.AddThemeStyleboxOverride("background", bgStyle);
|
||||
bar.AddThemeStyleboxOverride("fill", fillStyle);
|
||||
vbox.AddChild(bar);
|
||||
|
||||
var deadline = new Label { Text = $"Deadline: {demand.Deadline} coups" };
|
||||
deadline.AddThemeFontSizeOverride("font_size", 10);
|
||||
deadline.AddThemeColorOverride("font_color", new Color("#AAAAAA"));
|
||||
deadline.AddThemeColorOverride("font_color", new Color("#777777"));
|
||||
vbox.AddChild(deadline);
|
||||
|
||||
AddChild(vbox);
|
||||
_entries[demand.Position] = (label, bar);
|
||||
_entries[demand.Position] = (label, bar, deadline);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -54,9 +72,24 @@ public partial class ObjectivePanel : VBoxContainer
|
|||
if (!_entries.TryGetValue(demandCell, out var entry)) return;
|
||||
|
||||
entry.label.Text = $"{name}: {current}/{required}";
|
||||
entry.bar.Value = current;
|
||||
|
||||
// Animate the progress bar value
|
||||
var tween = entry.bar.CreateTween();
|
||||
tween.TweenProperty(entry.bar, "value", (double)current, 0.2f)
|
||||
.SetEase(Tween.EaseType.Out).SetTrans(Tween.TransitionType.Cubic);
|
||||
|
||||
if (current >= required)
|
||||
entry.label.AddThemeColorOverride("font_color", new Color("#44CC44"));
|
||||
{
|
||||
entry.label.AddThemeColorOverride("font_color", new Color("#5AAC5A")); // warm green
|
||||
|
||||
// Flash the progress bar green
|
||||
var fillStyle = new StyleBoxFlat
|
||||
{
|
||||
BgColor = new Color("#5AAC5A"),
|
||||
CornerRadiusTopLeft = 3, CornerRadiusTopRight = 3,
|
||||
CornerRadiusBottomLeft = 3, CornerRadiusBottomRight = 3
|
||||
};
|
||||
entry.bar.AddThemeStyleboxOverride("fill", fillStyle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue