This commit is contained in:
Savya Bikram Shah
2026-06-02 12:48:55 +05:45
parent 28f431f26a
commit 46b5bcc978
27 changed files with 1855 additions and 80 deletions

View File

@@ -14,6 +14,11 @@ public interface IColoringController
void PaintRegion(string regionId, Color color);
IReadOnlyDictionary<string, Color> GetCurrentColors();
UniTask PlayCompletionAnimationAsync(CancellationToken ct);
// True while the completion celebration is on screen (the real drawing is hidden).
// Capture must skip these frames so it never grabs the animation instead of the art.
bool IsPlayingCompletionAnimation { get; }
bool HasNonAuthoredColors { get; }
void ResetAll();
void Clear();

View File

@@ -16,6 +16,7 @@ namespace Darkmatter.Core.Data.Static.Features.ShapeBuilder
[Header("Drag")]
[SerializeField, Range(1f, 2f)] private float dragScale = 1.15f;
[SerializeField, Range(0f, 1f)] private float dragAlpha = 0.7f;
[Header("Preview easing")]
[SerializeField] private AnimationCurve previewCurve = AnimationCurve.EaseInOut(0, 0, 1, 1);
@@ -25,6 +26,7 @@ namespace Darkmatter.Core.Data.Static.Features.ShapeBuilder
public float SnapDuration => snapDuration;
public float ReturnDuration => returnDuration;
public float DragScale => dragScale;
public float DragAlpha => dragAlpha;
public AnimationCurve PreviewCurve => previewCurve;
public Vector2 DragSizeDelta(ShapeSO shape) =>

View File

@@ -1,6 +1,7 @@
using System.Threading;
using Cysharp.Threading.Tasks;
using Darkmatter.Core.Contracts.Features.Capture;
using Darkmatter.Core.Contracts.Features.Coloring;
using Darkmatter.Core.Contracts.Features.DrawingCatalog;
using Darkmatter.Core.Contracts.Features.GameplayFlow;
using Darkmatter.Core.Contracts.Features.Progression;
@@ -21,6 +22,7 @@ namespace Darkmatter.Features.Capture
private readonly CaptureConfig _config;
private readonly IProgressionSystem _progression;
private readonly IDrawingTemplateCatalog _catalog;
private readonly IColoringController _coloring;
public CaptureSystem(
ICaptureService captureService,
@@ -29,7 +31,8 @@ namespace Darkmatter.Features.Capture
IEventBus bus,
CaptureConfig config,
IProgressionSystem progression,
IDrawingTemplateCatalog catalog)
IDrawingTemplateCatalog catalog,
IColoringController coloring)
{
_captureService = captureService;
_galleryService = galleryService;
@@ -38,10 +41,16 @@ namespace Darkmatter.Features.Capture
_config = config;
_progression = progression;
_catalog = catalog;
_coloring = coloring;
}
public async UniTask<byte[]> CapturePngAsync(bool saveToGallery = false, CancellationToken ct = default)
{
// The completion celebration hides the real drawing and shows an animation object
// under PaperRoot. Capturing then would grab the animation, not the art, so skip.
// A null result keeps the existing thumbnail and skips the gallery write (both no-op on null).
if (_coloring.IsPlayingCompletionAnimation) return null;
var png = await _captureService.CapturePngAsync(_refs.PaperRoot.gameObject, _config.CaptureScale, ct);
if (!saveToGallery || png == null || png.Length == 0) return png;

View File

@@ -33,6 +33,7 @@ public class ColoringController : IColoringController, IDisposable
private GameObject _colorButtonPrefab;
private GameObject _completionAnimationInstance;
private CompletionAnimationView _completionAnimationView;
private bool _isPlayingCompletionAnimation;
private readonly List<ColorRegionView> _regions = new();
private readonly List<ColorButton> _buttons = new();
private readonly Dictionary<string, Color> _authoredColors = new();
@@ -88,17 +89,21 @@ public class ColoringController : IColoringController, IDisposable
return snapshot;
}
public bool IsPlayingCompletionAnimation => _isPlayingCompletionAnimation;
public async UniTask PlayCompletionAnimationAsync(CancellationToken ct)
{
if (_completionAnimationInstance == null || _completionAnimationView == null) return;
if (_colorInstance != null) _colorInstance.SetActive(false);
_completionAnimationInstance.SetActive(true);
_isPlayingCompletionAnimation = true;
try
{
await _completionAnimationView.PlayAsync(ct);
}
finally
{
_isPlayingCompletionAnimation = false;
if (_completionAnimationInstance != null)
_completionAnimationInstance.SetActive(false);
}
@@ -133,6 +138,7 @@ public class ColoringController : IColoringController, IDisposable
public void Clear()
{
_history.Drop();
_isPlayingCompletionAnimation = false;
foreach (var button in _buttons)
if (button != null)

View File

@@ -136,6 +136,12 @@ namespace Darkmatter.Features.GameplayFlow.Systems
public async UniTask NextAsync(CancellationToken ct)
{
// Drop any debounced autosave so it can't fire during/after the completion
// animation and capture the animation (or an empty paper) into the thumbnail.
_autosaveCts?.Cancel();
_autosaveCts?.Dispose();
_autosaveCts = null;
await SaveCurrentAsync(ct);
_refs.Confetti.Play();
_sfx.Play(SfxId.FireWorkLaunch);

View File

@@ -36,6 +36,7 @@ namespace Darkmatter.Features.ShapeBuilder.UI
private Vector2 _origAnchorMax;
private Vector2 _origPivot;
private bool _origPreserveAspect;
private Vector3 _homeScale = Vector3.one;
// Per-drag state
private RectTransform _rt;
@@ -45,6 +46,8 @@ namespace Darkmatter.Features.ShapeBuilder.UI
private Vector2 _trayPosInDragRoot;
private Vector2 _dragSizeDelta;
private Vector3 _dragLocalScale;
private float _dragOrigAlpha;
private Tween _dragScaleTween;
private Sequence _previewSeq;
private Sequence _snapSettle;
private bool _locked;
@@ -68,6 +71,14 @@ namespace Darkmatter.Features.ShapeBuilder.UI
if (image != null) image.color = color;
}
private void SetAlpha(float a)
{
if (image == null) return;
var c = image.color;
c.a = a;
image.color = c;
}
public void Setup(
ShapeSO shape,
SlotMarker[] candidateSlots,
@@ -95,6 +106,7 @@ namespace Darkmatter.Features.ShapeBuilder.UI
_origAnchorMax = RectTransform.anchorMax;
_origPivot = RectTransform.pivot;
_origPreserveAspect = image != null && image.preserveAspect;
_homeScale = RectTransform.localScale;
image.sprite = shape.Sprite;
ApplyTrayPose();
@@ -104,6 +116,10 @@ namespace Darkmatter.Features.ShapeBuilder.UI
{
if (_locked) return;
// Kill any tweens still running on this piece (e.g. a return/preview from a
// re-grab mid-animation) so the new drag starts from a clean, known pose.
Tween.StopAll(onTarget: RectTransform);
if (_dragRoot != null && RectTransform.parent != _dragRoot)
{
RectTransform.SetParent(_dragRoot, worldPositionStays: true);
@@ -114,11 +130,20 @@ namespace Darkmatter.Features.ShapeBuilder.UI
_eventCam = e.pressEventCamera;
_trayPosInDragRoot = RectTransform.anchoredPosition;
_dragSizeDelta = RectTransform.sizeDelta;
_dragLocalScale = RectTransform.localScale;
// Use the canonical resting scale, not the live value, so a re-grab mid-tween
// can't bake a drifted scale into the drag base.
_dragLocalScale = _homeScale;
RectTransform.localScale = _homeScale;
_grabOffset = RectTransform.anchoredPosition - ScreenToLocal(e.position);
_inPreview = false;
Tween.LocalScale(RectTransform, _dragLocalScale * _cfg.DragScale, _cfg.SnapDuration, Ease.OutQuad);
if (image != null)
{
_dragOrigAlpha = image.color.a;
SetAlpha(_cfg.DragAlpha);
}
_dragScaleTween = Tween.Scale(RectTransform, _dragLocalScale * _cfg.DragScale, _cfg.SnapDuration, Ease.OutQuad);
}
public void OnDrag(PointerEventData e)
@@ -156,6 +181,12 @@ namespace Darkmatter.Features.ShapeBuilder.UI
{
if (_locked) return;
// Stop the begin-drag scale-up so it can't fight the return/snap pose.
// Leave _previewSeq alive — the snap path lets it settle.
if (_dragScaleTween.isAlive) _dragScaleTween.Stop();
SetAlpha(_dragOrigAlpha);
var target = FindSlotUnder(e.position);
if (target != null)
{
@@ -184,6 +215,7 @@ namespace Darkmatter.Features.ShapeBuilder.UI
private void AnimatePreviewPose(bool toSlot)
{
if (_dragScaleTween.isAlive) _dragScaleTween.Stop();
if (_previewSeq.isAlive) _previewSeq.Stop();
if (toSlot && _activeSlot != null)
@@ -361,6 +393,7 @@ namespace Darkmatter.Features.ShapeBuilder.UI
{
RectTransform.sizeDelta = _traySize;
RectTransform.localRotation = Quaternion.identity;
RectTransform.localScale = _homeScale;
}
private Vector2 ScreenToLocal(Vector2 screenPos)