Polish transformer visuals with copper flash and new cargo colors

CellView gains FlashTransform: bright copper pulse + brief scale punch
(0.45s) on CargoConvertedEvent, distinct from the warmer golden
FlashProduce used by productions. EventAnimator routes transformer
conversions through the new phase so the flash reads as a conversion
moment rather than a second production tick.

PieceView picks up cargo colors for Tools (copper), Arms (deep red),
and Gold, matching the cargo-slide particle palette — pieces carrying
those transformed cargoes now render the correct indicator instead of
defaulting to white.
This commit is contained in:
Samuel Bouchet 2026-04-17 22:42:03 +02:00
parent 8a377c2e41
commit 2d2375e569
4 changed files with 58 additions and 15 deletions

View file

@ -134,4 +134,24 @@ public partial class CellView : Node2D
.SetEase(Tween.EaseType.Out).SetTrans(Tween.TransitionType.Cubic); .SetEase(Tween.EaseType.Out).SetTrans(Tween.TransitionType.Cubic);
tween.TweenCallback(Callable.From(() => _highlight.Visible = false)); tween.TweenCallback(Callable.From(() => _highlight.Visible = false));
} }
/// <summary>
/// Transformer conversion flash: bright copper pulse + scale punch.
/// Uses a distinct color from production so the two phases read apart.
/// </summary>
public void FlashTransform(float duration = 0.45f)
{
_highlight.Color = new Color(1f, 0.5f, 0.15f, 0.7f); // bright copper
_highlight.Visible = true;
var tween = CreateTween();
tween.SetParallel(true);
tween.TweenProperty(_highlight, "color", new Color(1f, 0.5f, 0.15f, 0f), duration)
.SetEase(Tween.EaseType.Out).SetTrans(Tween.TransitionType.Cubic);
tween.TweenProperty(_background, "scale", new Vector2(1.08f, 1.08f), duration * 0.45f)
.SetEase(Tween.EaseType.Out);
tween.Chain().TweenProperty(_background, "scale", Vector2.One, duration * 0.55f)
.SetEase(Tween.EaseType.InOut);
tween.Chain().TweenCallback(Callable.From(() => _highlight.Visible = false));
}
} }

View file

@ -25,6 +25,9 @@ public partial class PieceView : Node2D
private static readonly Color QueenColor = new("#8E3D5A"); // deep burgundy private static readonly Color QueenColor = new("#8E3D5A"); // deep burgundy
private static readonly Color WoodCargoColor = new("#A67C32"); private static readonly Color WoodCargoColor = new("#A67C32");
private static readonly Color StoneCargoColor = new("#7A7A7A"); private static readonly Color StoneCargoColor = new("#7A7A7A");
private static readonly Color ToolsCargoColor = new("#C87533");
private static readonly Color ArmsCargoColor = new("#8B0000");
private static readonly Color GoldCargoColor = new("#FFD700");
private static readonly Color ShadowColor = new Color(0, 0, 0, 0.18f); 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) public void Setup(int pieceId, PieceKind kind, Coords startCell, Coords endCell, BoardView boardView)
@ -136,6 +139,9 @@ public partial class PieceView : Node2D
{ {
CargoType.Wood => WoodCargoColor, CargoType.Wood => WoodCargoColor,
CargoType.Stone => StoneCargoColor, CargoType.Stone => StoneCargoColor,
CargoType.Tools => ToolsCargoColor,
CargoType.Arms => ArmsCargoColor,
CargoType.Gold => GoldCargoColor,
_ => Colors.White _ => Colors.White
}; };

View file

@ -80,6 +80,7 @@ public partial class EventAnimator : Node
tween.SetParallel(false); tween.SetParallel(false);
var produceEvents = new List<CargoProducedEvent>(); var produceEvents = new List<CargoProducedEvent>();
var transformerEvents = new List<CargoConvertedEvent>();
var transferEvents = new List<IWorldEvent>(); var transferEvents = new List<IWorldEvent>();
var moveEvents = new List<PieceMovedEvent>(); var moveEvents = new List<PieceMovedEvent>();
var collisionEvents = new List<PieceReturnedToStockEvent>(); var collisionEvents = new List<PieceReturnedToStockEvent>();
@ -92,7 +93,7 @@ public partial class EventAnimator : Node
switch (evt) switch (evt)
{ {
case TurnStartedEvent ts: case TurnStartedEvent ts:
FlushPhases(tween, produceEvents, transferEvents, moveEvents, collisionEvents); FlushPhases(tween, produceEvents, transformerEvents, transferEvents, moveEvents, collisionEvents);
tween.TweenCallback(Callable.From(() => _controlBar.UpdateTurn(ts.TurnNumber))); tween.TweenCallback(Callable.From(() => _controlBar.UpdateTurn(ts.TurnNumber)));
break; break;
@ -101,8 +102,7 @@ public partial class EventAnimator : Node
break; break;
case CargoConvertedEvent converted: case CargoConvertedEvent converted:
// Visual flash on transformer cell (treat like a produce event for animation) transformerEvents.Add(converted);
produceEvents.Add(new CargoProducedEvent(converted.TurnNumber, converted.TransformerCell, converted.OutputCargo));
break; break;
case CargoTransferredEvent: case CargoTransferredEvent:
@ -119,7 +119,7 @@ public partial class EventAnimator : Node
break; break;
case MissionCompleteEvent: case MissionCompleteEvent:
FlushPhases(tween, produceEvents, transferEvents, moveEvents, collisionEvents); FlushPhases(tween, produceEvents, transformerEvents, transferEvents, moveEvents, collisionEvents);
tween.TweenCallback(Callable.From(() => tween.TweenCallback(Callable.From(() =>
{ {
SfxManager.Instance?.PlayVictory(); SfxManager.Instance?.PlayVictory();
@ -130,7 +130,7 @@ public partial class EventAnimator : Node
break; break;
case MissionStartedEvent: case MissionStartedEvent:
FlushPhases(tween, produceEvents, transferEvents, moveEvents, collisionEvents); FlushPhases(tween, produceEvents, transformerEvents, transferEvents, moveEvents, collisionEvents);
tween.TweenCallback(Callable.From(() => tween.TweenCallback(Callable.From(() =>
{ {
EmitSignal(SignalName.MissionAdvanced); EmitSignal(SignalName.MissionAdvanced);
@ -142,7 +142,7 @@ public partial class EventAnimator : Node
break; break;
case TurnEndedEvent: case TurnEndedEvent:
FlushPhases(tween, produceEvents, transferEvents, moveEvents, collisionEvents); FlushPhases(tween, produceEvents, transformerEvents, transferEvents, moveEvents, collisionEvents);
break; break;
default: default:
@ -150,7 +150,7 @@ public partial class EventAnimator : Node
} }
} }
FlushPhases(tween, produceEvents, transferEvents, moveEvents, collisionEvents); FlushPhases(tween, produceEvents, transformerEvents, transferEvents, moveEvents, collisionEvents);
tween.TweenCallback(Callable.From(() => tween.TweenCallback(Callable.From(() =>
{ {
@ -162,10 +162,29 @@ public partial class EventAnimator : Node
private void FlushPhases( private void FlushPhases(
Tween tween, Tween tween,
List<CargoProducedEvent> produceEvents, List<CargoProducedEvent> produceEvents,
List<CargoConvertedEvent> transformerEvents,
List<IWorldEvent> transferEvents, List<IWorldEvent> transferEvents,
List<PieceMovedEvent> moveEvents, List<PieceMovedEvent> moveEvents,
List<PieceReturnedToStockEvent> collisionEvents) List<PieceReturnedToStockEvent> collisionEvents)
{ {
// Phase 1a: Transformer conversions — copper flash, distinct from production
if (transformerEvents.Count > 0)
{
var captured = transformerEvents.ToList();
tween.TweenCallback(Callable.From(() =>
{
SfxManager.Instance?.PlayProduce();
foreach (var evt in captured)
{
var cell = _boardView.GetCellView(evt.TransformerCell);
cell?.FlashTransform(0.45f);
SpawnProduceParticles(evt.TransformerCell, evt.OutputCargo);
}
}));
tween.TweenInterval(0.45f);
transformerEvents.Clear();
}
// Phase 1: Produce — warm golden flash + particle burst // Phase 1: Produce — warm golden flash + particle burst
if (produceEvents.Count > 0) if (produceEvents.Count > 0)
{ {

View file

@ -20,14 +20,12 @@ les surfaces d'interaction et d'animation.
Fou → Dame + 2 transformateurs). La vision GDD/plan prevoit une campagne Fou → Dame + 2 transformateurs). La vision GDD/plan prevoit une campagne
plus longue et un final orchestrant toutes les chaines. plus longue et un final orchestrant toutes les chaines.
### 2.2 Demandes recurrentes (post-campagne 1) ### 2.2 Demandes recurrentes — wiring restant
Pour forcer la preservation des automatisations entre missions : Le moteur gere le mode recurrent (`ConsumptionPerTurn`, `SustainTurns`,
- Une demande consomme N unites par tour. shortage tracking, events `DemandShortageStarted/Cleared`). Il reste :
- Si plus approvisionnee → etat "en penurie". - Concevoir des missions utilisant le mode recurrent (post-campagne 1).
- Condition : aucune demande en penurie pendant X tours consecutifs. - Visualisation UI du shortage (jauge buffer rouge, pulsation d'alerte).
- Ajuster la condition de fin si un mix classique + recurrent coexiste.
Implique : nouveau champ sur `DemandState`, flag shortage, regle de victoire
parametrable par mission. A valider via playtest avant d'ajouter au scope.
--- ---