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);
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 WoodCargoColor = new("#A67C32");
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);
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.Stone => StoneCargoColor,
CargoType.Tools => ToolsCargoColor,
CargoType.Arms => ArmsCargoColor,
CargoType.Gold => GoldCargoColor,
_ => Colors.White
};

View file

@ -80,6 +80,7 @@ public partial class EventAnimator : Node
tween.SetParallel(false);
var produceEvents = new List<CargoProducedEvent>();
var transformerEvents = new List<CargoConvertedEvent>();
var transferEvents = new List<IWorldEvent>();
var moveEvents = new List<PieceMovedEvent>();
var collisionEvents = new List<PieceReturnedToStockEvent>();
@ -92,7 +93,7 @@ public partial class EventAnimator : Node
switch (evt)
{
case TurnStartedEvent ts:
FlushPhases(tween, produceEvents, transferEvents, moveEvents, collisionEvents);
FlushPhases(tween, produceEvents, transformerEvents, transferEvents, moveEvents, collisionEvents);
tween.TweenCallback(Callable.From(() => _controlBar.UpdateTurn(ts.TurnNumber)));
break;
@ -101,8 +102,7 @@ public partial class EventAnimator : Node
break;
case CargoConvertedEvent converted:
// Visual flash on transformer cell (treat like a produce event for animation)
produceEvents.Add(new CargoProducedEvent(converted.TurnNumber, converted.TransformerCell, converted.OutputCargo));
transformerEvents.Add(converted);
break;
case CargoTransferredEvent:
@ -119,7 +119,7 @@ public partial class EventAnimator : Node
break;
case MissionCompleteEvent:
FlushPhases(tween, produceEvents, transferEvents, moveEvents, collisionEvents);
FlushPhases(tween, produceEvents, transformerEvents, transferEvents, moveEvents, collisionEvents);
tween.TweenCallback(Callable.From(() =>
{
SfxManager.Instance?.PlayVictory();
@ -130,7 +130,7 @@ public partial class EventAnimator : Node
break;
case MissionStartedEvent:
FlushPhases(tween, produceEvents, transferEvents, moveEvents, collisionEvents);
FlushPhases(tween, produceEvents, transformerEvents, transferEvents, moveEvents, collisionEvents);
tween.TweenCallback(Callable.From(() =>
{
EmitSignal(SignalName.MissionAdvanced);
@ -142,7 +142,7 @@ public partial class EventAnimator : Node
break;
case TurnEndedEvent:
FlushPhases(tween, produceEvents, transferEvents, moveEvents, collisionEvents);
FlushPhases(tween, produceEvents, transformerEvents, transferEvents, moveEvents, collisionEvents);
break;
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(() =>
{
@ -162,10 +162,29 @@ public partial class EventAnimator : Node
private void FlushPhases(
Tween tween,
List<CargoProducedEvent> produceEvents,
List<CargoConvertedEvent> transformerEvents,
List<IWorldEvent> transferEvents,
List<PieceMovedEvent> moveEvents,
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
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
plus longue et un final orchestrant toutes les chaines.
### 2.2 Demandes recurrentes (post-campagne 1)
Pour forcer la preservation des automatisations entre missions :
- Une demande consomme N unites par tour.
- Si plus approvisionnee → etat "en penurie".
- Condition : aucune demande en penurie pendant X tours consecutifs.
Implique : nouveau champ sur `DemandState`, flag shortage, regle de victoire
parametrable par mission. A valider via playtest avant d'ajouter au scope.
### 2.2 Demandes recurrentes — wiring restant
Le moteur gere le mode recurrent (`ConsumptionPerTurn`, `SustainTurns`,
shortage tracking, events `DemandShortageStarted/Cleared`). Il reste :
- Concevoir des missions utilisant le mode recurrent (post-campagne 1).
- Visualisation UI du shortage (jauge buffer rouge, pulsation d'alerte).
- Ajuster la condition de fin si un mix classique + recurrent coexiste.
---