Add collision camera pan/zoom and toast notification
EventAnimator now emits CollisionOccurred at the end of the collision
phase, carrying the struck cell and victim/destroyer identity. Main pans
and zooms the camera onto the cell over 0.45s and shows a fading toast
("Pion détruit par Tour — retourné au stock", or "collision mutuelle"
for same-status ties). The toast fades out after 3s and leaves the
camera framing the collision so the player can inspect the aftermath
before resuming.
This commit is contained in:
parent
1522b70398
commit
c4f6ecbf44
4 changed files with 104 additions and 6 deletions
|
|
@ -40,6 +40,7 @@ public partial class Main : Node2D
|
||||||
private Camera2D _camera = null!;
|
private Camera2D _camera = null!;
|
||||||
private ColorRect _fadeOverlay = null!;
|
private ColorRect _fadeOverlay = null!;
|
||||||
private FlavorBanner _flavorBanner = null!;
|
private FlavorBanner _flavorBanner = null!;
|
||||||
|
private Label _collisionToast = null!;
|
||||||
|
|
||||||
// Simulation timer
|
// Simulation timer
|
||||||
private Godot.Timer _simTimer = null!;
|
private Godot.Timer _simTimer = null!;
|
||||||
|
|
@ -394,6 +395,27 @@ public partial class Main : Node2D
|
||||||
_titleScreen.SetAnchorsPreset(Control.LayoutPreset.FullRect);
|
_titleScreen.SetAnchorsPreset(Control.LayoutPreset.FullRect);
|
||||||
uiRoot.AddChild(_titleScreen);
|
uiRoot.AddChild(_titleScreen);
|
||||||
|
|
||||||
|
// --- Collision toast (hidden by default, shown on collision) ---
|
||||||
|
_collisionToast = new Label
|
||||||
|
{
|
||||||
|
Text = "",
|
||||||
|
Visible = false,
|
||||||
|
MouseFilter = Control.MouseFilterEnum.Ignore
|
||||||
|
};
|
||||||
|
_collisionToast.AddThemeFontSizeOverride("font_size", 14);
|
||||||
|
_collisionToast.AddThemeColorOverride("font_color", new Color("#FFE8D0"));
|
||||||
|
_collisionToast.AddThemeColorOverride("font_outline_color", new Color(0, 0, 0, 0.8f));
|
||||||
|
_collisionToast.AddThemeConstantOverride("outline_size", 4);
|
||||||
|
_collisionToast.AnchorLeft = 0.0f;
|
||||||
|
_collisionToast.AnchorRight = 0.0f;
|
||||||
|
_collisionToast.AnchorTop = 1.0f;
|
||||||
|
_collisionToast.AnchorBottom = 1.0f;
|
||||||
|
_collisionToast.OffsetLeft = 20;
|
||||||
|
_collisionToast.OffsetRight = 400;
|
||||||
|
_collisionToast.OffsetTop = -ControlBarHeight - 60;
|
||||||
|
_collisionToast.OffsetBottom = -ControlBarHeight - 20;
|
||||||
|
uiRoot.AddChild(_collisionToast);
|
||||||
|
|
||||||
// --- Flavor Banner (narrative text) ---
|
// --- Flavor Banner (narrative text) ---
|
||||||
_flavorBanner = new FlavorBanner();
|
_flavorBanner = new FlavorBanner();
|
||||||
_flavorBanner.AnchorLeft = 0.1f;
|
_flavorBanner.AnchorLeft = 0.1f;
|
||||||
|
|
@ -430,6 +452,7 @@ public partial class Main : Node2D
|
||||||
_eventAnimator.TurnAnimationCompleted += OnTurnAnimationCompleted;
|
_eventAnimator.TurnAnimationCompleted += OnTurnAnimationCompleted;
|
||||||
_eventAnimator.VictoryReached += OnCampaignComplete;
|
_eventAnimator.VictoryReached += OnCampaignComplete;
|
||||||
_eventAnimator.MissionAdvanced += OnMissionAdvanced;
|
_eventAnimator.MissionAdvanced += OnMissionAdvanced;
|
||||||
|
_eventAnimator.CollisionOccurred += OnCollisionOccurred;
|
||||||
_metricsOverlay.NextLevelPressed += OnBackToMenu;
|
_metricsOverlay.NextLevelPressed += OnBackToMenu;
|
||||||
_detailPanel.RemoveRequested += OnRemoveRequested;
|
_detailPanel.RemoveRequested += OnRemoveRequested;
|
||||||
_inputMapper.CellClicked += OnCellClicked;
|
_inputMapper.CellClicked += OnCellClicked;
|
||||||
|
|
@ -871,6 +894,44 @@ public partial class Main : Node2D
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnCollisionOccurred(int col, int row, string victimKind, int destroyerId)
|
||||||
|
{
|
||||||
|
if (_sim == null) return;
|
||||||
|
|
||||||
|
// Pan + slight zoom to the collision cell
|
||||||
|
var cellPixel = _boardView.CoordsToPixel(new Coords(col, row));
|
||||||
|
var originalZoom = _camera.Zoom;
|
||||||
|
var targetZoom = originalZoom * 1.4f;
|
||||||
|
var tween = CreateTween();
|
||||||
|
tween.SetParallel(true);
|
||||||
|
tween.TweenProperty(_camera, "position", cellPixel, 0.45f)
|
||||||
|
.SetEase(Tween.EaseType.Out).SetTrans(Tween.TransitionType.Cubic);
|
||||||
|
tween.TweenProperty(_camera, "zoom", targetZoom, 0.45f)
|
||||||
|
.SetEase(Tween.EaseType.Out).SetTrans(Tween.TransitionType.Cubic);
|
||||||
|
|
||||||
|
// Compose toast message
|
||||||
|
string message;
|
||||||
|
if (destroyerId < 0)
|
||||||
|
{
|
||||||
|
message = $"{victimKind} détruit (collision mutuelle) — retourné au stock";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var snap = _sim.GetSnapshot();
|
||||||
|
var dp = snap.Pieces.FirstOrDefault(p => p.Id == destroyerId);
|
||||||
|
var destroyer = dp?.Kind.ToString() ?? "?";
|
||||||
|
message = $"{victimKind} détruit par {destroyer} — retourné au stock";
|
||||||
|
}
|
||||||
|
_collisionToast.Text = message;
|
||||||
|
_collisionToast.Modulate = new Color(1, 1, 1, 0);
|
||||||
|
_collisionToast.Visible = true;
|
||||||
|
var fadeIn = _collisionToast.CreateTween();
|
||||||
|
fadeIn.TweenProperty(_collisionToast, "modulate:a", 1f, 0.2f);
|
||||||
|
fadeIn.TweenInterval(3.0f);
|
||||||
|
fadeIn.TweenProperty(_collisionToast, "modulate:a", 0f, 0.5f);
|
||||||
|
fadeIn.TweenCallback(Callable.From(() => _collisionToast.Visible = false));
|
||||||
|
}
|
||||||
|
|
||||||
private void OnUndo()
|
private void OnUndo()
|
||||||
{
|
{
|
||||||
if (_sim == null) return;
|
if (_sim == null) return;
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,8 @@ public partial class EventAnimator : Node
|
||||||
public delegate void VictoryReachedEventHandler();
|
public delegate void VictoryReachedEventHandler();
|
||||||
[Signal]
|
[Signal]
|
||||||
public delegate void MissionAdvancedEventHandler();
|
public delegate void MissionAdvancedEventHandler();
|
||||||
|
[Signal]
|
||||||
|
public delegate void CollisionOccurredEventHandler(int col, int row, string victimKind, int destroyerId);
|
||||||
|
|
||||||
public void Initialize(BoardView boardView, ObjectivePanel objectivePanel,
|
public void Initialize(BoardView boardView, ObjectivePanel objectivePanel,
|
||||||
ControlBar controlBar, MetricsOverlay metricsOverlay)
|
ControlBar controlBar, MetricsOverlay metricsOverlay)
|
||||||
|
|
@ -268,6 +270,12 @@ public partial class EventAnimator : Node
|
||||||
dt.TweenProperty(pv, "modulate:a", 0f, DestroyDuration);
|
dt.TweenProperty(pv, "modulate:a", 0f, DestroyDuration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Emit signal for Main to pan camera + show toast
|
||||||
|
var first = captured[0];
|
||||||
|
EmitSignal(SignalName.CollisionOccurred,
|
||||||
|
first.Cell.Col, first.Cell.Row, first.Kind.ToString(),
|
||||||
|
first.DestroyerPieceId ?? -1);
|
||||||
}));
|
}));
|
||||||
tween.TweenInterval(DestroyDuration);
|
tween.TweenInterval(DestroyDuration);
|
||||||
tween.TweenCallback(Callable.From(() =>
|
tween.TweenCallback(Callable.From(() =>
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,6 @@ et l'extension de la campagne.
|
||||||
Le moteur expose deja les commandes et events requis ; cote Godot il manque
|
Le moteur expose deja les commandes et events requis ; cote Godot il manque
|
||||||
les surfaces d'interaction et d'animation.
|
les surfaces d'interaction et d'animation.
|
||||||
|
|
||||||
### 1.2 Drag & drop des pieces placees
|
|
||||||
`MovePieceCommand` + `PieceMovedByPlayerEvent` existent cote engine.
|
|
||||||
Cote Godot : permettre de glisser une piece placee pour deplacer son point de
|
|
||||||
depart. Les arrivees legales se recalculent pendant le drag. Application
|
|
||||||
atomique au relachement (entre deux tours).
|
|
||||||
|
|
||||||
### 1.3 Collision — camera pan/zoom + notification
|
### 1.3 Collision — camera pan/zoom + notification
|
||||||
L'engine emet deja `PieceReturnedToStockEvent` + auto-pause. Il reste a :
|
L'engine emet deja `PieceReturnedToStockEvent` + auto-pause. Il reste a :
|
||||||
- Animer un pan + zoom de la camera vers la case de collision.
|
- Animer un pan + zoom de la camera vers la case de collision.
|
||||||
|
|
|
||||||
35
tools/automation/test_collision.py
Normal file
35
tools/automation/test_collision.py
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
"""Smoke test for collision camera pan + notification toast."""
|
||||||
|
import sys, time
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
|
||||||
|
|
||||||
|
from tools.automation.harness import Harness
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
with Harness.launch(run_name="collision") as h:
|
||||||
|
h.load_mission("campaign_01", 0)
|
||||||
|
|
||||||
|
# Two Pawns that will collide on (1,0) at turn 1 — mutual destruction
|
||||||
|
h.place("Pawn", (0, 0), (1, 0))
|
||||||
|
h.place("Pawn", (2, 0), (1, 0))
|
||||||
|
h.screenshot("01_before_collision")
|
||||||
|
|
||||||
|
h.set_speed(0.2)
|
||||||
|
h.play()
|
||||||
|
time.sleep(1.5)
|
||||||
|
|
||||||
|
h.screenshot("02_during_pan_zoom")
|
||||||
|
time.sleep(1.0)
|
||||||
|
h.screenshot("03_toast_visible")
|
||||||
|
|
||||||
|
s = h.state()
|
||||||
|
print(f"[after] phase={s['phase']} pieces={len(s['pieces'])} stock={s['remainingStock']}")
|
||||||
|
assert s['phase'] == 'Paused', f"Expected auto-pause after collision, got {s['phase']}"
|
||||||
|
assert len(s['pieces']) == 0, "Both pawns should have been returned to stock"
|
||||||
|
print("OK — collision auto-pause, pieces returned to stock")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Loading…
Add table
Reference in a new issue