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 ColorRect _fadeOverlay = null!;
|
||||
private FlavorBanner _flavorBanner = null!;
|
||||
private Label _collisionToast = null!;
|
||||
|
||||
// Simulation timer
|
||||
private Godot.Timer _simTimer = null!;
|
||||
|
|
@ -394,6 +395,27 @@ public partial class Main : Node2D
|
|||
_titleScreen.SetAnchorsPreset(Control.LayoutPreset.FullRect);
|
||||
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) ---
|
||||
_flavorBanner = new FlavorBanner();
|
||||
_flavorBanner.AnchorLeft = 0.1f;
|
||||
|
|
@ -430,6 +452,7 @@ public partial class Main : Node2D
|
|||
_eventAnimator.TurnAnimationCompleted += OnTurnAnimationCompleted;
|
||||
_eventAnimator.VictoryReached += OnCampaignComplete;
|
||||
_eventAnimator.MissionAdvanced += OnMissionAdvanced;
|
||||
_eventAnimator.CollisionOccurred += OnCollisionOccurred;
|
||||
_metricsOverlay.NextLevelPressed += OnBackToMenu;
|
||||
_detailPanel.RemoveRequested += OnRemoveRequested;
|
||||
_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()
|
||||
{
|
||||
if (_sim == null) return;
|
||||
|
|
|
|||
|
|
@ -41,6 +41,8 @@ public partial class EventAnimator : Node
|
|||
public delegate void VictoryReachedEventHandler();
|
||||
[Signal]
|
||||
public delegate void MissionAdvancedEventHandler();
|
||||
[Signal]
|
||||
public delegate void CollisionOccurredEventHandler(int col, int row, string victimKind, int destroyerId);
|
||||
|
||||
public void Initialize(BoardView boardView, ObjectivePanel objectivePanel,
|
||||
ControlBar controlBar, MetricsOverlay metricsOverlay)
|
||||
|
|
@ -268,6 +270,12 @@ public partial class EventAnimator : Node
|
|||
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.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
|
||||
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
|
||||
L'engine emet deja `PieceReturnedToStockEvent` + auto-pause. Il reste a :
|
||||
- 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