Compare commits

...

14 Commits

Author SHA1 Message Date
378b00096a Merge pull request 'work_branch' (#13) from work_branch into main
Reviewed-on: #13
2026-05-29 10:40:47 +02:00
Mausham
6d9b096e8e added anim 2026-05-29 14:24:03 +05:45
Mausham
919169157e added some features in artbook 2026-05-29 12:47:21 +05:45
96e69c3d1a Merge pull request 'work_branch' (#12) from work_branch into main
Reviewed-on: #12
2026-05-29 06:52:37 +02:00
Mausham
a51a73efdd fixess 2026-05-29 10:37:07 +05:45
Mausham
3914adaa84 Merge remote-tracking branch 'origin/main' into work_branch
# Conflicts:
#	Assets/Darkmatter/Code/Features/ColorbookFlow/Features.Colorbook.asmdef
#	Assets/Darkmatter/Code/Features/ColorbookFlow/System/ColorbookFlowController.cs
2026-05-29 10:35:04 +05:45
Mausham
4e8f2ae7ad fixes 2026-05-29 10:31:14 +05:45
bdac5b485f Merge pull request 'Game flow and coloring done' (#11) from savya into main
Reviewed-on: #11
2026-05-29 06:45:39 +02:00
Savya Bikram Shah
b3c096a150 Game flow and coloring done 2026-05-28 19:45:15 +05:45
Mausham
9899a8b549 code added for Artbook 2026-05-28 18:48:08 +05:45
Mausham
820f614415 some additional fixes 2026-05-28 18:32:14 +05:45
92aa1d2751 Merge pull request 'Refactored and Animaiton for HOlder' (#10) from savya into main
Reviewed-on: #10
2026-05-28 14:02:15 +02:00
Savya Bikram Shah
9ac752b23c Refactored and Animaiton for HOlder 2026-05-28 17:45:58 +05:45
252c1b8a41 Merge pull request 'work_branch' (#9) from work_branch into main
Reviewed-on: #9
2026-05-28 13:24:44 +02:00
245 changed files with 63641 additions and 4426 deletions

View File

@@ -0,0 +1,17 @@
using System.Collections.Generic;
using System.Threading;
using Cysharp.Threading.Tasks;
using Darkmatter.Core.Contracts.Features.DrawingCatalog;
using UnityEngine;
namespace Darkmatter.Core.Contracts.Features.Coloring;
public interface IColoringController
{
UniTask InitializeRegionsAsync(IDrawingTemplate template, IReadOnlyDictionary<string, Color> savedColors,
CancellationToken ct);
void PaintRegion(string regionId, Color color);
IReadOnlyDictionary<string, Color> GetCurrentColors();
void Clear();
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b5ba5a7108234751ac286a57795fac06
timeCreated: 1779971202

View File

@@ -11,6 +11,4 @@ public interface IDrawingCatalogController
event Action ListChanged;
UniTask InitializeAsync(CancellationToken ct);
void OnTemplateSelected(string id);
void PublishBackBtnClickedSignal();
void PublishOpenArtBookSignal();
}

View File

@@ -10,8 +10,10 @@ namespace Darkmatter.Core.Contracts.Features.DrawingCatalog
string Id { get; }
string DisplayName { get; }
Sprite DefaultThumbnail { get; }
GameObject Prefab { get; }
GameObject DrawingPrefab { get; }
GameObject ColoringPrefab { get; }
IReadOnlyList<ShapeSO> Pieces { get; }
IReadOnlyList<ColorRegionDTO> Regions { get; }
string ColorPaletteId { get; }
}
}

View File

@@ -0,0 +1,12 @@
using Cysharp.Threading.Tasks;
namespace Darkmatter.Core.Contracts.Features.GameplayFlow
{
public interface IGameplayFlowController
{
UniTask BackAsync();
UniTask SaveAsync();
UniTask NextAsync();
void OnApplicationPaused();
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: fee5907f5643d492ab8ba177f84c30f6

View File

@@ -5,9 +5,4 @@ namespace Darkmatter.Core.Contracts.Features.GameplayFlow;
public interface IGameplaySceneRefs
{
RectTransform PaperRoot { get; }
RectTransform SlotsParent { get; }
RectTransform PiecesParent { get; }
RectTransform RegionsParent { get; }
RectTransform HudRoot { get; }
RectTransform TrayPanel { get; }
}

View File

@@ -7,9 +7,11 @@ namespace Darkmatter.Core.Contracts.Features.History
event Action OnStackChanged;
bool CanUndo { get; }
bool CanRedo { get; }
void Push(ICommand cmd); // executes + appends
void Push(ICommand cmd);
void Append(ICommand cmd);
void Undo();
void Redo();
void Clear();
void Clear(); // reverts every command via Undo, then drops (user-facing "wipe" semantics)
void Drop(); // drops the list without reverting (controller cleanup — avoids touching destroyed views)
}
}

View File

@@ -12,6 +12,9 @@ namespace Darkmatter.Core.Contracts.Features.Progression
IReadOnlyCollection<string> CompletedTemplateIds { get; }
void MarkCompleted(string templateId);
string LastOpenedTemplateId { get; }
UniTask SetLastOpenedAsync(string templateId);
DrawingProgress? GetProgress(string templateId);
UniTask SaveProgressAsync(DrawingProgress progress);
UniTask SaveProgressAsync(DrawingProgress progress, byte[] thumbnailPng);

View File

@@ -1,18 +1,19 @@
using UnityEngine;
using UnityEngine.UI;
namespace Darkmatter.Core.Data.Dynamic.Features.Coloring
{
public readonly struct ColorRegionDTO
{
public string RegionId { get; }
public Sprite Sprite { get; }
public string Id { get; }
public Image Image { get; }
public Vector2 AnchoredPosition { get; }
public Color InitialColor { get; }
public ColorRegionDTO(string regionId, Sprite sprite, Vector2 anchoredPosition, Color initialColor)
public ColorRegionDTO(string id, Image image, Vector2 anchoredPosition, Color initialColor)
{
RegionId = regionId;
Sprite = sprite;
Id = id;
Image = image;
AnchoredPosition = anchoredPosition;
InitialColor = initialColor;
}

View File

@@ -7,5 +7,6 @@ namespace Darkmatter.Core.Data.Dynamic.Features.Progression
public struct ProgressionRootDto
{
public List<DrawingProgress> records;
public string lastOpenedTemplateId;
}
}

View File

@@ -0,0 +1,6 @@
using UnityEngine;
namespace Darkmatter.Core
{
public record struct OpenColorBookSignal;
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 73bddc8d591de2a438044c2a9ee180be

View File

@@ -2,6 +2,6 @@ using UnityEngine;
namespace Darkmatter.Core
{
public record struct BackBtnClickedSignal;
public record struct ReturnToMainMenuSignal;
}

View File

@@ -0,0 +1,4 @@
namespace Darkmatter.Core.Data.Signals.Features.ShapeBuilder
{
public record struct ShapeBuilderStartedSignal(string TemplateId);
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 40f2733e19ad34fe5ba5d63aa2e9a39b

View File

@@ -12,8 +12,10 @@ namespace Darkmatter.Core.Data.Static.Features.DrawingTemplate
[field: SerializeField] public string Id { get; private set; }
[field: SerializeField] public string DisplayName { get; private set; }
[field: SerializeField] public Sprite DefaultThumbnail { get; private set; }
[field: SerializeField] public GameObject Prefab { get; private set; }
[field: SerializeField] public GameObject DrawingPrefab { get; private set; }
[field: SerializeField] public GameObject ColoringPrefab { get; private set; }
[field: SerializeField] public IReadOnlyList<ShapeSO> Pieces { get; private set; }
[field: SerializeField] public IReadOnlyList<ColorRegionDTO> Regions { get; private set; }
[field: SerializeField] public string ColorPaletteId { get; private set; } = "defaultPalette";
}
}

View File

@@ -8,7 +8,6 @@ namespace Darkmatter.Core.Data.Static.Features.ShapeBuilder
{
[Header("Radii (canvas units; reference resolution 2048x2048)")]
[SerializeField] private float snapRadius = 100f;
[SerializeField] private float snapGraceMultiplier = 1.5f;
[SerializeField] private float previewRadius = 200f;
[Header("Tween durations (seconds)")]

View File

@@ -6,5 +6,6 @@ namespace Darkmatter.Core.Enums.Services.Audio
ShapeHover = 100,
ShapeSnap = 101,
ShapeReturn = 102,
UiTap = 200,
}
}

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5d8eacd004e3e0649b6ff4a6b58c952c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,21 @@
{
"name": "Features.Artbook",
"rootNamespace": "Darkmatter.Features.Artbook",
"references": [
"GUID:6a0a834eb41764f12ba55c3fb04a40cb",
"GUID:b0214a6008ed146ff8f122a6a9c2f6cc",
"GUID:c1c03c0e5b2f4412b9f2be1c20d6a9b1",
"GUID:b4c9f7fbf1e144933a1797dc208ece5f",
"GUID:f51ebe6a0ceec4240a699833d6309b23",
"GUID:6055be8ebefd69e48b49212b09b47b2f"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: dd1597db9436d6f4da32f1391b905651
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: af74b0a63b3e4a746885f0e4b911cd91
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,17 @@
using Darkmatter.Libs.Installers;
using UnityEngine;
using VContainer;
using VContainer.Unity;
namespace Darkmatter.Features.Artbook
{
public class ArtBookFeatureModule : MonoBehaviour, IModule
{
[SerializeField] private ArtbookView artbookView;
public void Register(IContainerBuilder builder)
{
if (artbookView != null) builder.RegisterEntryPoint<ArtbookPresenter>().WithParameter(artbookView);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: e42bea53c6fa23c45a7c85e9b025bed7

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 4a13b92fe0335e04c8de0c5cbc54f6fb
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,7 @@
using System;
using UnityEngine;
namespace Darkmatter.Features.Artbook
{
public record struct ArtbookEntry(string Id, string Name, Texture2D Thumbnail, DateTime UpdatedUtc);
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 6821df5d7eb2fe345aeeebd1290d84b3

View File

@@ -0,0 +1,201 @@
using System;
using System.Collections.Generic;
using System.Threading;
using Cysharp.Threading.Tasks;
using Darkmatter.Core;
using Darkmatter.Core.Contracts.Features.DrawingCatalog;
using Darkmatter.Core.Contracts.Features.Progression;
using Darkmatter.Core.Contracts.Services.Gallery;
using Darkmatter.Core.Data.Signals.Features.Drawing;
using Darkmatter.Libs.Observer;
using UnityEngine;
using VContainer.Unity;
namespace Darkmatter.Features.Artbook
{
public class ArtbookPresenter : IStartable, IDisposable
{
private const int EntriesPerSpread = 2;
private readonly ArtbookView _view;
private readonly IEventBus _eventBus;
private readonly IProgressionSystem _progression;
private readonly IDrawingTemplateCatalog _catalog;
private readonly IGalleryService _gallery;
private readonly List<ArtbookEntry> _entries = new();
private readonly List<Sprite> _ownedSprites = new();
private readonly List<Texture2D> _ownedTextures = new();
private CancellationTokenSource _cts;
private IDisposable _openSubscription;
private int _currentSpread;
public ArtbookPresenter(
ArtbookView view,
IEventBus eventBus,
IProgressionSystem progression,
IDrawingTemplateCatalog catalog,
IGalleryService gallery)
{
_view = view;
_eventBus = eventBus;
_progression = progression;
_catalog = catalog;
_gallery = gallery;
}
public void Start()
{
_view.OnBackButtonClicked += HandleBackButtonClicked;
_view.OnColorbookButtonClicked += HandleColorbookButtonClicked;
_view.OnPrevSpreadClicked += HandlePrevSpreadClicked;
_view.OnNextSpreadClicked += HandleNextSpreadClicked;
_view.OnLeftSaveClicked += HandleLeftSaveClicked;
_view.OnRightSaveClicked += HandleRightSaveClicked;
_view.OnLeftEditClicked += HandleLeftEditClicked;
_view.OnRightEditClicked += HandleRightEditClicked;
_openSubscription = _eventBus.Subscribe<OpenArtBookSignal>(HandleOpenArtbookEvent);
}
private void HandleOpenArtbookEvent(OpenArtBookSignal signal)
{
_view.Show();
_cts?.Cancel();
_cts?.Dispose();
_cts = new CancellationTokenSource();
LoadAndShowAsync(_cts.Token).Forget();
}
private async UniTaskVoid LoadAndShowAsync(CancellationToken ct)
{
ClearOwnedAssets();
_entries.Clear();
_currentSpread = 0;
await _catalog.FetchAsync();
if (ct.IsCancellationRequested) return;
foreach (var id in _catalog.AllTemplateIds)
{
if (ct.IsCancellationRequested) return;
var progress = _progression.GetProgress(id);
if (progress is not { hasThumbnail: true }) continue;
var texture = await _progression.GetCachedThumbnailAsync(id);
if (ct.IsCancellationRequested) return;
if (texture == null) continue;
var template = await _catalog.LoadAsync(id);
if (ct.IsCancellationRequested) return;
_ownedTextures.Add(texture);
_entries.Add(new ArtbookEntry(id, template.DisplayName, texture, progress.Value.UpdatedUtc));
}
_entries.Sort((a, b) => b.UpdatedUtc.CompareTo(a.UpdatedUtc));
RenderSpread();
}
private void HandlePrevSpreadClicked()
{
if (_currentSpread <= 0) return;
_currentSpread--;
RenderSpread();
}
private void HandleNextSpreadClicked()
{
if (_currentSpread >= TotalSpreads - 1) return;
_currentSpread++;
RenderSpread();
}
private int TotalSpreads => Mathf.Max(1, Mathf.CeilToInt(_entries.Count / (float)EntriesPerSpread));
private void RenderSpread()
{
_view.SetSpread(GetLeftEntry(), SpriteFor(GetLeftEntry()), GetRightEntry(), SpriteFor(GetRightEntry()));
_view.SetNavigation(_currentSpread > 0, _currentSpread < TotalSpreads - 1);
}
private ArtbookEntry? GetLeftEntry()
{
var idx = _currentSpread * EntriesPerSpread;
return idx < _entries.Count ? _entries[idx] : null;
}
private ArtbookEntry? GetRightEntry()
{
var idx = _currentSpread * EntriesPerSpread + 1;
return idx < _entries.Count ? _entries[idx] : null;
}
private Sprite SpriteFor(ArtbookEntry? entry)
{
if (!entry.HasValue) return null;
var tex = entry.Value.Thumbnail;
if (tex == null) return null;
var sprite = Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), new Vector2(0.5f, 0.5f), 100f);
_ownedSprites.Add(sprite);
return sprite;
}
private void HandleLeftSaveClicked() => SaveToGalleryAsync(GetLeftEntry()).Forget();
private void HandleRightSaveClicked() => SaveToGalleryAsync(GetRightEntry()).Forget();
private async UniTaskVoid SaveToGalleryAsync(ArtbookEntry? entry)
{
if (!entry.HasValue || entry.Value.Thumbnail == null) return;
var ct = _cts?.Token ?? CancellationToken.None;
await _gallery.SaveImageAsync(entry.Value.Thumbnail, entry.Value.Id, ct);
}
private void HandleLeftEditClicked() => OpenForEdit(GetLeftEntry());
private void HandleRightEditClicked() => OpenForEdit(GetRightEntry());
private void OpenForEdit(ArtbookEntry? entry)
{
if (!entry.HasValue) return;
_eventBus.Publish(new DrawingSelectedSignal(entry.Value.Id));
_view.Hide();
}
private void HandleBackButtonClicked()
{
_eventBus.Publish(new OpenColorBookSignal());
_view.Hide();
}
private void HandleColorbookButtonClicked()
{
_eventBus.Publish(new OpenColorBookSignal());
_view.Hide();
}
private void ClearOwnedAssets()
{
foreach (var s in _ownedSprites) UnityEngine.Object.Destroy(s);
foreach (var t in _ownedTextures) UnityEngine.Object.Destroy(t);
_ownedSprites.Clear();
_ownedTextures.Clear();
}
public void Dispose()
{
_view.OnBackButtonClicked -= HandleBackButtonClicked;
_view.OnColorbookButtonClicked -= HandleColorbookButtonClicked;
_view.OnPrevSpreadClicked -= HandlePrevSpreadClicked;
_view.OnNextSpreadClicked -= HandleNextSpreadClicked;
_view.OnLeftSaveClicked -= HandleLeftSaveClicked;
_view.OnRightSaveClicked -= HandleRightSaveClicked;
_view.OnLeftEditClicked -= HandleLeftEditClicked;
_view.OnRightEditClicked -= HandleRightEditClicked;
_openSubscription?.Dispose();
_cts?.Cancel();
_cts?.Dispose();
ClearOwnedAssets();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: ce45172769c70e044a56095bc9da281a

View File

@@ -0,0 +1,92 @@
using System;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace Darkmatter.Features.Artbook
{
public class ArtbookView : MonoBehaviour
{
[SerializeField] private Button backBtn;
[SerializeField] private Button colorbookBtn;
[Header("Pages")]
[SerializeField] private Image leftPageImage;
[SerializeField] private Image rightPageImage;
[SerializeField] private TMP_Text leftPageNameText;
[SerializeField] private TMP_Text rightPageNameText;
[Header("Page Actions")]
[SerializeField] private Button leftSaveBtn;
[SerializeField] private Button rightSaveBtn;
[SerializeField] private Button leftEditBtn;
[SerializeField] private Button rightEditBtn;
[Header("Navigation")]
[SerializeField] private Button leftArrowBtn;
[SerializeField] private Button rightArrowBtn;
public event Action OnBackButtonClicked;
public event Action OnColorbookButtonClicked;
public event Action OnPrevSpreadClicked;
public event Action OnNextSpreadClicked;
public event Action OnLeftSaveClicked;
public event Action OnRightSaveClicked;
public event Action OnLeftEditClicked;
public event Action OnRightEditClicked;
void Start()
{
backBtn.onClick.AddListener(() => OnBackButtonClicked?.Invoke());
colorbookBtn.onClick.AddListener(() => OnColorbookButtonClicked?.Invoke());
leftArrowBtn.onClick.AddListener(() => OnPrevSpreadClicked?.Invoke());
rightArrowBtn.onClick.AddListener(() => OnNextSpreadClicked?.Invoke());
leftSaveBtn.onClick.AddListener(() => OnLeftSaveClicked?.Invoke());
rightSaveBtn.onClick.AddListener(() => OnRightSaveClicked?.Invoke());
leftEditBtn.onClick.AddListener(() => OnLeftEditClicked?.Invoke());
rightEditBtn.onClick.AddListener(() => OnRightEditClicked?.Invoke());
}
public void Show()
{
gameObject.SetActive(true);
}
public void Hide()
{
gameObject.SetActive(false);
}
public void SetSpread(ArtbookEntry? left, Sprite leftSprite, ArtbookEntry? right, Sprite rightSprite)
{
ApplyPage(leftPageImage, leftPageNameText, leftSaveBtn, leftEditBtn, left, leftSprite);
ApplyPage(rightPageImage, rightPageNameText, rightSaveBtn, rightEditBtn, right, rightSprite);
}
public void SetNavigation(bool canPrev, bool canNext)
{
leftArrowBtn.interactable = canPrev;
rightArrowBtn.interactable = canNext;
}
private static void ApplyPage(Image image, TMP_Text nameText, Button saveBtn, Button editBtn,
ArtbookEntry? entry, Sprite sprite)
{
var hasEntry = entry.HasValue;
saveBtn.interactable = hasEntry;
editBtn.interactable = hasEntry;
if (!hasEntry)
{
image.enabled = false;
image.sprite = null;
nameText.text = string.Empty;
return;
}
image.sprite = sprite;
image.enabled = sprite != null;
nameText.text = entry.Value.Name;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d635629f252db53479e66347a37c4f0b

View File

@@ -4,8 +4,11 @@
"references": [
"GUID:6a0a834eb41764f12ba55c3fb04a40cb",
"GUID:c1c03c0e5b2f4412b9f2be1c20d6a9b1",
"GUID:c176ee863a5e74e88a6517f9f102cf92",
"GUID:b4c9f7fbf1e144933a1797dc208ece5f",
"GUID:b0214a6008ed146ff8f122a6a9c2f6cc",
"GUID:f51ebe6a0ceec4240a699833d6309b23"
"GUID:f51ebe6a0ceec4240a699833d6309b23",
"GUID:80ecb87cae9c44d19824e70ea7229748"
],
"includePlatforms": [],
"excludePlatforms": [],

View File

@@ -1,26 +1,82 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Cysharp.Threading.Tasks;
using Darkmatter.Core;
using Darkmatter.Core.Contracts.Features.DrawingCatalog;
using Darkmatter.Core.Contracts.Features.Loading;
using Darkmatter.Core.Contracts.Features.Progression;
using Darkmatter.Core.Contracts.Services.Scenes;
using Darkmatter.Core.Data.Signals.Features.Drawing;
using Darkmatter.Core.Enums.Services.Scenes;
using Darkmatter.Libs.Observer;
using VContainer.Unity;
namespace Darkmatter.Features.Colorbook.System;
public class ColorbookFlowController : IAsyncStartable
public class ColorbookFlowController : IAsyncStartable, IDisposable
{
private readonly IDrawingCatalogController _drawingCatalog;
private readonly ILoadingScreen _loadingScreen;
private readonly IProgressionSystem _progression;
private readonly ISceneService _scenes;
private readonly IEventBus _bus;
public ColorbookFlowController(IDrawingCatalogController drawingCatalog, ILoadingScreen loadingScreen)
private IDisposable _selectedSub;
private IDisposable _returnToMainMenuSubscription;
public ColorbookFlowController(
IDrawingCatalogController drawingCatalog,
ILoadingScreen loadingScreen,
IProgressionSystem progression,
ISceneService scenes,
IEventBus bus)
{
_drawingCatalog = drawingCatalog;
_loadingScreen = loadingScreen;
_progression = progression;
_scenes = scenes;
_bus = bus;
}
public async UniTask StartAsync(CancellationToken cancellation = new CancellationToken())
{
_returnToMainMenuSubscription = _bus.Subscribe<ReturnToMainMenuSignal>(OnReturnToMainMenu);
_loadingScreen.SetProgress(1f);
await _drawingCatalog.InitializeAsync(cancellation);
_selectedSub = _bus.Subscribe<DrawingSelectedSignal>(OnDrawingSelected);
_loadingScreen.Hide();
}
private void OnReturnToMainMenu(ReturnToMainMenuSignal signal)
{
LoadMainMenuSceneAsync().Forget(UnityEngine.Debug.LogException);
}
private async UniTask LoadMainMenuSceneAsync(CancellationToken ct = default)
{
_loadingScreen.Show();
var progress = new Progress<float>(p => _loadingScreen.SetProgress(p * 0.5f));
await _scenes.LoadSceneAsync(nameof(GameScene.MainMenu), progress, ct);
var mappedProgress = new Progress<float>(p => _loadingScreen.SetProgress(0.5f + p * 0.25f));
await _scenes.UnloadSceneAsync(nameof(GameScene.Colorbook), mappedProgress, ct);
}
private void OnDrawingSelected(DrawingSelectedSignal signal)
{
HandleSelectionAsync(signal.TemplateId).Forget();
}
private async UniTaskVoid HandleSelectionAsync(string templateId)
{
await _progression.SetLastOpenedAsync(templateId);
await _scenes.LoadSceneAsync(GameScene.Gameplay, progress: null, cancellationToken: default);
}
public void Dispose()
{
_selectedSub?.Dispose();
_returnToMainMenuSubscription?.Dispose();
}
}

View File

@@ -0,0 +1,22 @@
using Darkmatter.Core.Contracts.Features.History;
using Darkmatter.Features.Coloring.UI;
using UnityEngine;
namespace Darkmatter.Features.Coloring.Commands;
public class ColorRegionCommand : ICommand
{
private readonly ColorRegionView _view;
private readonly Color _from;
private readonly Color _to;
public ColorRegionCommand(ColorRegionView view, Color from, Color to)
{
_view = view;
_from = from;
_to = to;
}
public void Execute() => _view.SetColor(_to);
public void Undo() => _view.SetColor(_from);
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c212dc9aaa7e4445968fee229a715b3b
timeCreated: 1779972906

View File

@@ -1,14 +1,28 @@
using Darkmatter.Core.Contracts.Features.Coloring;
using Darkmatter.Features.Coloring.Systems;
using Darkmatter.Features.Coloring.UI;
using Darkmatter.Libs.Installers;
using UnityEngine;
using VContainer;
using VContainer.Unity;
namespace Darkmatter.Features.Coloring
{
public class ColoringFeatureModule : MonoBehaviour,IModule
public class ColoringFeatureModule : MonoBehaviour, IModule
{
[SerializeField] private ColorPaletteHolderView paletteHolderView;
public void Register(IContainerBuilder builder)
{
if (paletteHolderView != null)
{
builder.RegisterComponent(paletteHolderView);
builder.RegisterEntryPoint<ColorPaletteHolderPresenter>().WithParameter(paletteHolderView);
}
builder.Register<IColorButtonFactory, ColorButtonFactory>(Lifetime.Singleton);
builder.Register<ColoringStateRepository>(Lifetime.Singleton);
builder.Register<IColoringController, ColoringController>(Lifetime.Singleton);
}
}
}

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 569e45483f54348a593b6571740cd8a2
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,30 @@
using Darkmatter.Core.Contracts.Services.Audio;
using Darkmatter.Features.Coloring.UI;
using UnityEngine;
namespace Darkmatter.Features.Coloring.Systems;
public class ColorButtonFactory : IColorButtonFactory
{
private readonly ColorPaletteHolderView _holder;
private readonly ColoringStateRepository _repository;
private readonly ISfxPlayer _sfx;
public ColorButtonFactory(
ColorPaletteHolderView holder,
ColoringStateRepository repository,
ISfxPlayer sfx)
{
_holder = holder;
_repository = repository;
_sfx = sfx;
}
public ColorButton Create(GameObject prefab, Color color)
{
var go = Object.Instantiate(prefab, _holder.SpawnRoot);
var btn = go.GetComponent<ColorButton>();
btn.Setup(color, _repository, _sfx);
return btn;
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1bba9a928a76471391068acd46c4f684
timeCreated: 1779970637

View File

@@ -0,0 +1,146 @@
using System;
using System.Collections.Generic;
using System.Threading;
using Cysharp.Threading.Tasks;
using Darkmatter.Core.Contracts.Features.Coloring;
using Darkmatter.Core.Contracts.Features.DrawingCatalog;
using Darkmatter.Core.Contracts.Features.GameplayFlow;
using Darkmatter.Core.Contracts.Features.History;
using Darkmatter.Core.Contracts.Services.Assets;
using Darkmatter.Core.Data.Signals.Features.Coloring;
using Darkmatter.Core.Data.Static.Features.Coloring;
using Darkmatter.Features.Coloring.Commands;
using Darkmatter.Features.Coloring.UI;
using Darkmatter.Libs.Observer;
using UnityEngine;
using ZLinq;
namespace Darkmatter.Features.Coloring.Systems;
public class ColoringController : IColoringController, IDisposable
{
private const string ColorButtonPrefabKey = "colorButton";
private readonly ColoringStateRepository _repository;
private readonly IColorButtonFactory _buttonFactory;
private readonly IEventBus _bus;
private readonly IAssetProviderService _assetProviderService;
private readonly IUndoStack _history;
private readonly IGameplaySceneRefs _refs;
private GameObject _colorInstance;
private GameObject _colorButtonPrefab;
private readonly List<ColorRegionView> _regions = new();
private readonly List<ColorButton> _buttons = new();
public ColoringController(
ColoringStateRepository repository,
IColorButtonFactory buttonFactory,
IEventBus bus,
IAssetProviderService assetProviderService,
IUndoStack history,
IGameplaySceneRefs refs)
{
_repository = repository;
_buttonFactory = buttonFactory;
_bus = bus;
_assetProviderService = assetProviderService;
_history = history;
_refs = refs;
}
public async UniTask InitializeRegionsAsync(IDrawingTemplate template,
IReadOnlyDictionary<string, Color> savedColors, CancellationToken ct)
{
Clear();
await TryLoadColorButtonPrefabAsync(ct);
ct.ThrowIfCancellationRequested();
await TryLoadColorPaletteAsync(template, ct);
ct.ThrowIfCancellationRequested();
CreateColorButtonInstances();
InitializeColorRegions(template, savedColors);
}
public void PaintRegion(string regionId, Color color)
{
var region = _regions.AsValueEnumerable().FirstOrDefault(r => r.RegionId == regionId);
if (region == null) return;
var from = region.Color;
_history.Push(new ColorRegionCommand(region, from, color));
_bus.Publish(new ColorAppliedSignal(regionId, color));
}
public IReadOnlyDictionary<string, Color> GetCurrentColors()
{
var snapshot = new Dictionary<string, Color>(_regions.Count);
foreach (var region in _regions)
snapshot[region.RegionId] = region.Color;
return snapshot;
}
public void Clear()
{
_history.Drop();
foreach (var button in _buttons)
if (button != null) UnityEngine.Object.Destroy(button.gameObject);
_buttons.Clear();
_regions.Clear();
if (_colorInstance != null)
{
UnityEngine.Object.Destroy(_colorInstance);
_colorInstance = null;
}
}
public void Dispose() => Clear();
private void InitializeColorRegions(IDrawingTemplate template, IReadOnlyDictionary<string, Color> savedColors)
{
_colorInstance = UnityEngine.Object.Instantiate(template.ColoringPrefab, _refs.PaperRoot);
var views = _colorInstance.GetComponentsInChildren<ColorRegionView>(includeInactive: true);
foreach (var region in views)
{
var id = region.RegionId;
var authoredColor = region.Color;
var resumeColor = savedColors != null && savedColors.TryGetValue(id, out var saved)
? saved
: authoredColor;
if (resumeColor != authoredColor)
_history.Append(new ColorRegionCommand(region, authoredColor, resumeColor));
_regions.Add(region);
region.Initialize(resumeColor, () => PaintRegion(id, _repository.SelectedColor));
}
}
private async UniTask TryLoadColorButtonPrefabAsync(CancellationToken ct)
{
if (_colorButtonPrefab != null) return;
_colorButtonPrefab = await _assetProviderService.LoadAssetAsync<GameObject>(
ColorButtonPrefabKey, progress: null, cancellationToken: ct);
if (_colorButtonPrefab == null)
throw new Exception($"No color button prefab at '{ColorButtonPrefabKey}'");
}
private async UniTask TryLoadColorPaletteAsync(IDrawingTemplate template, CancellationToken ct)
{
var palette = await _assetProviderService.LoadAssetAsync<ColorPaletteSO>(
template.ColorPaletteId, progress: null, cancellationToken: ct);
if (palette == null)
throw new Exception($"No color palette at '{template.ColorPaletteId}'");
_repository.SetPalette(palette);
}
private void CreateColorButtonInstances()
{
foreach (var color in _repository.SelectedPalette.Colors)
_buttons.Add(_buttonFactory.Create(_colorButtonPrefab, color));
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0849f8cc757842278c38ee6f4d61b985
timeCreated: 1779971175

View File

@@ -0,0 +1,33 @@
using System;
using Darkmatter.Core.Contracts.Features.Coloring;
using UnityEngine;
namespace Darkmatter.Features.Coloring.Systems;
public class ColoringStateRepository
{
public IColorPalette SelectedPalette { get; private set; }
public int SelectedIndex { get; private set; }
public Color SelectedColor =>
SelectedPalette != null && SelectedIndex >= 0 && SelectedIndex < SelectedPalette.Colors.Count
? SelectedPalette.Colors[SelectedIndex]
: Color.white;
public event Action SelectedIndexChanged;
public void SetPalette(IColorPalette palette)
{
SelectedPalette = palette;
SelectedIndex = 0;
SelectedIndexChanged?.Invoke();
}
public void SelectColor(Color color)
{
if (SelectedPalette == null) return;
var idx = SelectedPalette.Colors.IndexOf(color);
if (idx < 0) return;
SelectedIndex = idx;
SelectedIndexChanged?.Invoke();
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a288824e4f554000994bec451c09957c
timeCreated: 1779970861

View File

@@ -0,0 +1,9 @@
using Darkmatter.Features.Coloring.UI;
using UnityEngine;
namespace Darkmatter.Features.Coloring.Systems;
public interface IColorButtonFactory
{
ColorButton Create(GameObject prefab,Color color);
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 91b71be7f45a41cbb17492f82a3f0967
timeCreated: 1779970564

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 416f96737010f40c085ac404743fa8ef
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,54 @@
using Darkmatter.Core.Contracts.Services.Audio;
using Darkmatter.Core.Enums.Services.Audio;
using Darkmatter.Features.Coloring.Systems;
using UnityEngine;
using UnityEngine.UI;
namespace Darkmatter.Features.Coloring.UI
{
[RequireComponent(typeof(Button))]
public class ColorButton : MonoBehaviour
{
[SerializeField] private Image swatch;
[SerializeField] private GameObject selectedUI;
private Button _button;
private Color _color;
private ColoringStateRepository _repository;
private ISfxPlayer _sfx;
private void Awake()
{
_button = GetComponent<Button>();
}
public void Setup(Color color, ColoringStateRepository repository, ISfxPlayer sfx)
{
_color = color;
_repository = repository;
_sfx = sfx;
swatch.color = color;
_button.onClick.AddListener(OnClick);
_repository.SelectedIndexChanged += UpdateSelectedUI;
UpdateSelectedUI();
}
private void OnClick()
{
_sfx.Play(SfxId.UiTap);
_repository.SelectColor(_color);
}
private void UpdateSelectedUI()
{
if (selectedUI != null)
selectedUI.SetActive(_repository.SelectedColor == _color);
}
private void OnDestroy()
{
if (_button != null) _button.onClick.RemoveListener(OnClick);
if (_repository != null) _repository.SelectedIndexChanged -= UpdateSelectedUI;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7e64b829b90144faa3bd2c7f5c0ad291
timeCreated: 1779970210

View File

@@ -0,0 +1,32 @@
using System;
using Darkmatter.Core.Data.Signals.Features.ShapeBuilder;
using Darkmatter.Libs.Observer;
using VContainer.Unity;
namespace Darkmatter.Features.Coloring.UI
{
public class ColorPaletteHolderPresenter : IStartable, IDisposable
{
private readonly ColorPaletteHolderView _view;
private readonly IEventBus _bus;
private IDisposable _assembledSub;
public ColorPaletteHolderPresenter(ColorPaletteHolderView view, IEventBus bus)
{
_view = view;
_bus = bus;
}
public void Start()
{
_view.HideInstant();
_assembledSub = _bus.Subscribe<ShapeAssembledSignal>(_ => _view.Show());
}
public void Dispose()
{
_assembledSub?.Dispose();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: cc0590d8f6fce4487943575bfb96c32a

View File

@@ -0,0 +1,76 @@
using PrimeTween;
using UnityEngine;
namespace Darkmatter.Features.Coloring.UI
{
[RequireComponent(typeof(CanvasGroup))]
public class ColorPaletteHolderView : MonoBehaviour
{
[SerializeField] private RectTransform spawnRoot;
[SerializeField] private RectTransform animatedRoot;
[SerializeField] private float showDuration = 0.35f;
[SerializeField] private float hideDuration = 0.25f;
[SerializeField] private Vector2 hiddenOffset = new(1500f, 0f);
private CanvasGroup _canvasGroup;
private Vector2 _shownAnchoredPos;
private Sequence _activeSequence;
public RectTransform SpawnRoot => spawnRoot;
private void Awake()
{
_canvasGroup = GetComponent<CanvasGroup>();
if (animatedRoot == null) animatedRoot = (RectTransform)transform;
_shownAnchoredPos = animatedRoot.anchoredPosition;
}
public Sequence Show()
{
KillActive();
gameObject.SetActive(true);
_canvasGroup.interactable = true;
_canvasGroup.blocksRaycasts = true;
_activeSequence = Sequence.Create()
.Group(Tween.UIAnchoredPosition(animatedRoot, _shownAnchoredPos, showDuration, Ease.OutBack))
.Group(Tween.Alpha(_canvasGroup, 1f, showDuration, Ease.OutQuad));
return _activeSequence;
}
public Sequence Hide()
{
KillActive();
_canvasGroup.interactable = false;
_canvasGroup.blocksRaycasts = false;
var hiddenPos = _shownAnchoredPos + hiddenOffset;
_activeSequence = Sequence.Create()
.Group(Tween.UIAnchoredPosition(animatedRoot, hiddenPos, hideDuration, Ease.InQuad))
.Group(Tween.Alpha(_canvasGroup, 0f, hideDuration, Ease.InQuad))
.ChainCallback(() => gameObject.SetActive(false));
return _activeSequence;
}
public void HideInstant()
{
KillActive();
animatedRoot.anchoredPosition = _shownAnchoredPos + hiddenOffset;
_canvasGroup.alpha = 0f;
_canvasGroup.interactable = false;
_canvasGroup.blocksRaycasts = false;
gameObject.SetActive(false);
}
private void KillActive()
{
if (_activeSequence.isAlive) _activeSequence.Stop();
_activeSequence = default;
}
private void OnDisable()
{
KillActive();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c960fbf3dc5a54f5399178665d20b1ce

View File

@@ -0,0 +1,44 @@
using System;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace Darkmatter.Features.Coloring.UI
{
[RequireComponent(typeof(Image))]
public class ColorRegionView : MonoBehaviour, IPointerClickHandler
{
[field: SerializeField] public string RegionId { get; private set; }
[SerializeField, Range(0f, 1f)] private float alphaHitThreshold = 0.5f;
public Color Color => _image.color;
private Image _image;
private event Action OnRegionClicked;
private void Awake()
{
_image = GetComponent<Image>();
_image.alphaHitTestMinimumThreshold = alphaHitThreshold;
}
public void Initialize(Color color, Action onRegionClicked)
{
_image.color = color;
OnRegionClicked = onRegionClicked;
}
public void SetColor(Color color)
{
_image.color = color;
}
public void OnPointerClick(PointerEventData eventData)
{
OnRegionClicked?.Invoke();
}
private void OnDestroy()
{
OnRegionClicked = null;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 7667901d8aea645d28b5125a2ed546ce

View File

@@ -41,14 +41,6 @@ public sealed class DrawingCatalogController : IDrawingCatalogController
{
_bus.Publish(new DrawingSelectedSignal(id));
}
public void PublishBackBtnClickedSignal()
{
_bus.Publish(new BackBtnClickedSignal());
}
public void PublishOpenArtBookSignal()
{
_bus.Publish(new OpenArtBookSignal());
}
private void Refresh()
{

View File

@@ -2,43 +2,82 @@ using System;
using System.Collections.Generic;
using System.Threading;
using Cysharp.Threading.Tasks;
using Darkmatter.Core;
using Darkmatter.Core.Contracts.Features.DrawingCatalog;
using Darkmatter.Libs.Observer;
using VContainer.Unity;
using UnityEngine;
namespace Darkmatter.Features.DrawingCatalog
{
public class DrawingCatalogPresenter : IStartable, IDisposable
{
private int _currentPage = 0;
private int _totalPages = 1;
private const int ItemPerPage = 8; //2 rows x 4 columns
private readonly DrawingCatalogView _view;
private readonly IDrawingCatalogController _controller;
private readonly IDrawingTemplateCatalog _catalog;
private readonly IEventBus _eventBus;
private readonly CancellationTokenSource _cts = new();
private IDisposable _openSubscription;
public DrawingCatalogPresenter(DrawingCatalogView view, IDrawingCatalogController controller,
IDrawingTemplateCatalog catalog)
IDrawingTemplateCatalog catalog, IEventBus eventBus)
{
_view = view;
_controller = controller;
_catalog = catalog;
_eventBus = eventBus;
}
public void Start()
{
_view.OnItemClicked += OnItemClicked;
_view.OnBackClicked += OnBackBtnClicked;
_view.OnArtBookClicked += OnArtBookBtnClicked;
_view.OnLeftPageClicked += OnLeftPageBtnClicked;
_view.OnRightPageClicked += OnRightPageBtnClicked;
_controller.ListChanged += OnListChanged;
_openSubscription = _eventBus.Subscribe<OpenColorBookSignal>(HandleOpenColorBookSignal);
}
private void HandleOpenColorBookSignal(OpenColorBookSignal signal)
{
_view.Show();
}
private void OnRightPageBtnClicked()
{
if (_currentPage < _totalPages - 1)
{
_currentPage++;
_view.SetPagination(_currentPage, _totalPages);
}
}
private void OnLeftPageBtnClicked()
{
if (_currentPage > 0)
{
_currentPage--;
_view.SetPagination(_currentPage, _totalPages);
}
}
private void OnArtBookBtnClicked()
{
_controller.PublishOpenArtBookSignal();
_eventBus.Publish(new OpenArtBookSignal());
_view.Hide();
}
private void OnBackBtnClicked()
{
_controller.PublishBackBtnClickedSignal();
_eventBus.Publish(new ReturnToMainMenuSignal());
Debug.Log("Back button clicked in Drawing Catalog");
}
private void OnItemClicked(string id) =>
@@ -51,6 +90,10 @@ namespace Darkmatter.Features.DrawingCatalog
{
var ids = _controller.VisibleIds;
var vms = new List<CatalogItemVM>(ids.Count);
_totalPages = (int)Math.Ceiling(ids.Count / (float)ItemPerPage);
_currentPage = 0;
foreach (var id in ids)
{
if (ct.IsCancellationRequested) return;
@@ -59,13 +102,21 @@ namespace Darkmatter.Features.DrawingCatalog
}
if (ct.IsCancellationRequested) return;
_view.ResetScrollPosition();
_view.SetItems(vms);
_view.SetPagination(_currentPage, _totalPages);
}
public void Dispose()
{
_view.OnItemClicked -= OnItemClicked;
_controller.ListChanged -= OnListChanged;
_view.OnBackClicked -= OnBackBtnClicked;
_view.OnArtBookClicked -= OnArtBookBtnClicked;
_view.OnLeftPageClicked -= OnLeftPageBtnClicked;
_view.OnRightPageClicked -= OnRightPageBtnClicked;
_openSubscription?.Dispose();
_cts.Cancel();
_cts.Dispose();
}

View File

@@ -12,19 +12,38 @@ namespace Darkmatter.Features.DrawingCatalog
[SerializeField] private RectTransform content;
[SerializeField] private DrawingCatalogButton buttonPrefab;
[Header("Buttons")]
[Header("Header Buttons")]
[SerializeField] private Button backButton;
[SerializeField] private Button artBookButton;
[Header("Scroll Pagination")]
[SerializeField] private ScrollRect scrollRect;
[SerializeField] private Button leftPageButton;
[SerializeField] private Button rightPageButton;
private readonly List<DrawingCatalogButton> _buttons = new();
public event Action<string> OnItemClicked;
public event Action OnBackClicked;
public event Action OnArtBookClicked;
public event Action OnLeftPageClicked;
public event Action OnRightPageClicked;
private Coroutine _sliderCoroutine;
public void Start()
{
backButton.onClick.AddListener(() => OnBackClicked?.Invoke());
artBookButton.onClick.AddListener(() => OnArtBookClicked?.Invoke());
leftPageButton.onClick.AddListener(() => OnLeftPageClicked?.Invoke());
rightPageButton.onClick.AddListener(() => OnRightPageClicked?.Invoke());
}
public void Show()
{
gameObject.SetActive(true);
}
public void Hide()
{
gameObject.SetActive(false);
}
public void SetItems(IReadOnlyList<CatalogItemVM> items)
@@ -44,6 +63,45 @@ namespace Darkmatter.Features.DrawingCatalog
_buttons.RemoveAt(_buttons.Count - 1);
Destroy(last.gameObject);
}
LayoutRebuilder.ForceRebuildLayoutImmediate(content);
}
public void SetPagination(int currentPage, int totalPages)
{
leftPageButton.interactable = currentPage > 0;
rightPageButton.interactable = currentPage < totalPages - 1;
if (totalPages <= 1) return;
float pageWidth = scrollRect.viewport.rect.width;
float maxScroll = content.rect.width - pageWidth;
if (maxScroll <= 0f) return;
float targetPosition = Mathf.Clamp01(currentPage * pageWidth / maxScroll);
if (_sliderCoroutine != null)
StopCoroutine(_sliderCoroutine);
_sliderCoroutine = StartCoroutine(SmoothScrollTo(targetPosition));
}
private System.Collections.IEnumerator SmoothScrollTo(float targetPosition)
{
const float duration = 0.3f;
float elapsed = 0f;
float startPosition = scrollRect.horizontalNormalizedPosition;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
scrollRect.horizontalNormalizedPosition = Mathf.Lerp(startPosition, targetPosition, elapsed / duration);
yield return null;
}
scrollRect.horizontalNormalizedPosition = targetPosition;
}
public void ResetScrollPosition()
{
scrollRect.horizontalNormalizedPosition = 0f;
}
}
}

View File

@@ -3,8 +3,12 @@
"rootNamespace": "Darkmatter.Features.GameplayFlow",
"references": [
"GUID:6a0a834eb41764f12ba55c3fb04a40cb",
"GUID:c1c03c0e5b2f4412b9f2be1c20d6a9b1",
"GUID:c176ee863a5e74e88a6517f9f102cf92",
"GUID:b4c9f7fbf1e144933a1797dc208ece5f",
"GUID:b0214a6008ed146ff8f122a6a9c2f6cc",
"GUID:c1c03c0e5b2f4412b9f2be1c20d6a9b1"
"GUID:f51ebe6a0ceec4240a699833d6309b23",
"GUID:80ecb87cae9c44d19824e70ea7229748"
],
"includePlatforms": [],
"excludePlatforms": [],

View File

@@ -1,5 +1,6 @@
using Darkmatter.Core.Contracts.Features.GameplayFlow;
using Darkmatter.Features.GameplayFlow.SceneRefs;
using Darkmatter.Features.GameplayFlow.Systems;
using Darkmatter.Libs.Installers;
using UnityEngine;
using VContainer;
@@ -15,6 +16,10 @@ namespace Darkmatter.Features.GameplayFlow
{
if (sceneRefs != null)
builder.RegisterComponent<IGameplaySceneRefs>(sceneRefs);
builder.Register<GameplayFlowController>(Lifetime.Singleton)
.As<IGameplayFlowController>()
.As<IAsyncStartable>();
}
}
}

View File

@@ -6,19 +6,7 @@ namespace Darkmatter.Features.GameplayFlow.SceneRefs
public class GameplaySceneRefs : MonoBehaviour, IGameplaySceneRefs
{
[SerializeField] private RectTransform paperRoot;
[SerializeField] private RectTransform slotsParent;
[SerializeField] private RectTransform piecesParent;
[SerializeField] private RectTransform regionsParent;
[SerializeField] private RectTransform hudRoot;
[SerializeField] private RectTransform trayPanel;
public RectTransform PaperRoot => paperRoot;
public RectTransform SlotsParent => slotsParent;
public RectTransform PiecesParent => piecesParent;
public RectTransform RegionsParent => regionsParent;
public RectTransform HudRoot => hudRoot;
public RectTransform TrayPanel => trayPanel;
}
}

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ef06e7bcb7ee346d894886c4a4848bcd
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,230 @@
using System;
using System.Collections.Generic;
using System.Threading;
using Cysharp.Threading.Tasks;
using Darkmatter.Core.Contracts.Features.Coloring;
using Darkmatter.Core.Contracts.Features.DrawingCatalog;
using Darkmatter.Core.Contracts.Features.GameplayFlow;
using Darkmatter.Core.Contracts.Features.Progression;
using Darkmatter.Core.Contracts.Features.ShapeBuilder;
using Darkmatter.Core.Contracts.Services.Scenes;
using Darkmatter.Core.Data.Dynamic.Features.Progression;
using Darkmatter.Core.Data.Signals.Features.Coloring;
using Darkmatter.Core.Data.Signals.Features.ShapeBuilder;
using Darkmatter.Core.Enums.Features.Progression;
using Darkmatter.Core.Enums.Services.Scenes;
using Darkmatter.Libs.Observer;
using UnityEngine;
using VContainer.Unity;
namespace Darkmatter.Features.GameplayFlow.Systems
{
public sealed class GameplayFlowController : IAsyncStartable, IGameplayFlowController, IDisposable
{
private const int AutosaveDebounceMs = 500;
private readonly IProgressionSystem _progression;
private readonly IDrawingTemplateCatalog _catalog;
private readonly IShapeBuilderController _shapeBuilder;
private readonly IColoringController _coloring;
private readonly ISceneService _scenes;
private readonly IEventBus _bus;
private IDrawingTemplate _template;
private string _templateId;
private DrawingPhase _phase = DrawingPhase.ShapeBuilding;
private IDisposable _assembledSub;
private IDisposable _colorAppliedSub;
private CancellationTokenSource _autosaveCts;
private CancellationTokenSource _scopeCts;
public GameplayFlowController(
IProgressionSystem progression,
IDrawingTemplateCatalog catalog,
IShapeBuilderController shapeBuilder,
IColoringController coloring,
ISceneService scenes,
IEventBus bus)
{
_progression = progression;
_catalog = catalog;
_shapeBuilder = shapeBuilder;
_coloring = coloring;
_scenes = scenes;
_bus = bus;
}
public async UniTask StartAsync(CancellationToken cancellation)
{
_scopeCts = CancellationTokenSource.CreateLinkedTokenSource(cancellation);
var ct = _scopeCts.Token;
_templateId = _progression.LastOpenedTemplateId;
if (string.IsNullOrEmpty(_templateId))
throw new Exception("[GameplayFlow] No LastOpenedTemplateId set. ColorbookFlow must call _progression.SetLastOpenedAsync(id) before loading Gameplay.");
_template = await _catalog.LoadAsync(_templateId);
ct.ThrowIfCancellationRequested();
var progress = _progression.GetProgress(_templateId);
_phase = progress?.phase ?? DrawingPhase.ShapeBuilding;
await _shapeBuilder.InitializeAsync(ct);
ct.ThrowIfCancellationRequested();
_assembledSub = _bus.Subscribe<ShapeAssembledSignal>(OnShapeAssembled);
_colorAppliedSub = _bus.Subscribe<ColorAppliedSignal>(OnColorApplied);
if (_phase == DrawingPhase.Coloring)
{
// Resume direct into coloring: pre-snap every piece. ShapeBuilder
// emits ShapeAssembledSignal once counter hits expected; that path
// also triggers InitializeColoring with savedColors.
var allIds = CollectAllPieceIds(_template);
await _shapeBuilder.BuildAsync(_template, allIds, ct);
}
else
{
await _shapeBuilder.BuildAsync(_template, progress?.snappedPieces, ct);
}
}
public async UniTask BackAsync()
{
await SaveCurrentAsync(captureThumbnail: _phase == DrawingPhase.Coloring);
_shapeBuilder.Clear();
_coloring.Clear();
await _scenes.LoadSceneAsync(GameScene.Colorbook, progress: null, cancellationToken: default);
}
public async UniTask SaveAsync()
{
// Explicit user-pressed save — capture thumbnail + (planned) native gallery export.
await SaveCurrentAsync(captureThumbnail: true);
// TODO: route through ICaptureService + IGalleryService once those exist.
}
public async UniTask NextAsync()
{
await SaveCurrentAsync(captureThumbnail: true);
_progression.MarkCompleted(_templateId);
var nextId = _catalog.GetNextTemplate(_templateId);
if (string.IsNullOrEmpty(nextId))
{
await _scenes.LoadSceneAsync(GameScene.Colorbook, progress: null, cancellationToken: default);
return;
}
await _progression.SetLastOpenedAsync(nextId);
_shapeBuilder.Clear();
_coloring.Clear();
await _scenes.LoadSceneAsync(GameScene.Gameplay, progress: null, cancellationToken: default);
}
public void OnApplicationPaused()
{
// Fire-and-forget — pause window is small; PlayerPrefs write is sync under the hood.
SaveCurrentAsync(captureThumbnail: false).Forget();
}
private void OnShapeAssembled(ShapeAssembledSignal signal)
{
if (signal.TemplateId != _templateId) return;
EnterColoringAsync().Forget();
}
private async UniTask EnterColoringAsync()
{
_phase = DrawingPhase.Coloring;
var progress = _progression.GetProgress(_templateId);
var savedColors = ToColorDict(progress?.regionColors);
await _coloring.InitializeRegionsAsync(_template, savedColors, _scopeCts.Token);
// Bare-assembled snapshot — catalog should show the puzzle outline even if
// the child never paints (per readme §12b save matrix).
await SaveCurrentAsync(captureThumbnail: true);
}
private void OnColorApplied(ColorAppliedSignal _)
{
if (_phase != DrawingPhase.Coloring) return;
_autosaveCts?.Cancel();
_autosaveCts?.Dispose();
_autosaveCts = CancellationTokenSource.CreateLinkedTokenSource(_scopeCts.Token);
DebouncedAutosaveAsync(_autosaveCts.Token).Forget();
}
private async UniTaskVoid DebouncedAutosaveAsync(CancellationToken ct)
{
try
{
await UniTask.Delay(AutosaveDebounceMs, cancellationToken: ct);
await SaveCurrentAsync(captureThumbnail: false);
}
catch (OperationCanceledException) { /* superseded by next paint or scope end */ }
}
private async UniTask SaveCurrentAsync(bool captureThumbnail)
{
if (string.IsNullOrEmpty(_templateId)) return;
var snappedIds = new List<string>(_shapeBuilder.GetSnappedPieceIds());
var regionEntries = new List<RegionColorEntry>();
foreach (var kv in _coloring.GetCurrentColors())
regionEntries.Add(new RegionColorEntry { regionId = kv.Key, color = kv.Value });
var existing = _progression.GetProgress(_templateId);
var progress = new DrawingProgress
{
templateId = _templateId,
phase = _phase,
snappedPieces = snappedIds,
regionColors = regionEntries,
hasThumbnail = captureThumbnail || (existing?.hasThumbnail ?? false),
hasBeenCompleted = existing?.hasBeenCompleted ?? false,
completionCount = existing?.completionCount ?? 0,
UpdatedUtc = DateTime.UtcNow,
FirstCompletedUtc = existing?.FirstCompletedUtc,
};
byte[] thumbnailPng = null;
if (captureThumbnail)
{
// TODO: replace with ICaptureService.CaptureAsync() once available.
// For now we record hasThumbnail=true but write no PNG.
}
await _progression.SaveProgressAsync(progress, thumbnailPng);
}
private static IReadOnlyCollection<string> CollectAllPieceIds(IDrawingTemplate template)
{
var ids = new List<string>(template.Pieces.Count);
foreach (var p in template.Pieces) ids.Add(p.Id);
return ids;
}
private static IReadOnlyDictionary<string, Color> ToColorDict(List<RegionColorEntry> entries)
{
var dict = new Dictionary<string, Color>();
if (entries == null) return dict;
foreach (var e in entries)
if (!string.IsNullOrEmpty(e.regionId))
dict[e.regionId] = e.color;
return dict;
}
public void Dispose()
{
_assembledSub?.Dispose();
_colorAppliedSub?.Dispose();
_autosaveCts?.Cancel();
_autosaveCts?.Dispose();
_scopeCts?.Cancel();
_scopeCts?.Dispose();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 54c118b4507054746befcaa85a1e9578

View File

@@ -24,6 +24,14 @@ namespace Darkmatter.Features.History
OnStackChanged?.Invoke();
}
public void Append(ICommand cmd)
{
_undo.AddLast(cmd);
if (_undo.Count > Capacity) _undo.RemoveFirst();
_redo.Clear();
OnStackChanged?.Invoke();
}
public void Undo()
{
if (!CanUndo) return;
@@ -50,5 +58,12 @@ namespace Darkmatter.Features.History
_redo.Clear();
OnStackChanged?.Invoke();
}
public void Drop()
{
_undo.Clear();
_redo.Clear();
OnStackChanged?.Invoke();
}
}
}

View File

@@ -28,6 +28,8 @@ namespace Darkmatter.Features.MainMenuFlow.Flow
public UniTask StartAsync(CancellationToken cancellation = new CancellationToken())
{
_playBtnClickedSubscription = _eventBus.Subscribe<PlayBtnClickedSignal>(OnPlayBtnClicked);
_loadingScreen.SetProgress(1f);
_loadingScreen.Hide();
return UniTask.CompletedTask;
}

View File

@@ -14,8 +14,10 @@ public sealed class ProgressionRepository
private const string ThumbnailsFolder = "thumbnails";
private readonly Dictionary<string, DrawingProgress> _records = new();
private string _lastOpenedTemplateId;
public IEnumerable<DrawingProgress> AllProgress() => _records.Values;
public string LastOpenedTemplateId => _lastOpenedTemplateId;
public DrawingProgress? TryGet(string templateId) =>
_records.TryGetValue(templateId, out var p) ? p : (DrawingProgress?)null;
@@ -45,6 +47,14 @@ public sealed class ProgressionRepository
_records[rec.templateId] = rec;
}
_lastOpenedTemplateId = root.lastOpenedTemplateId;
return UniTask.CompletedTask;
}
public UniTask SetLastOpenedAsync(string templateId)
{
_lastOpenedTemplateId = templateId;
FlushRoot();
return UniTask.CompletedTask;
}
@@ -121,6 +131,7 @@ public sealed class ProgressionRepository
var root = new ProgressionRootDto
{
records = _records.Values.ToList(),
lastOpenedTemplateId = _lastOpenedTemplateId,
};
var json = JsonUtility.ToJson(root);
ProtectedPlayerPrefs.SetString(PlayerPrefsKeys.Progression, json);

View File

@@ -15,6 +15,10 @@ public class ProgressionSystem : IProgressionSystem
private readonly SemaphoreSlim _writeLock = new(1, 1);
public IReadOnlyCollection<string> CompletedTemplateIds => _completed;
public string LastOpenedTemplateId => _repository.LastOpenedTemplateId;
public UniTask SetLastOpenedAsync(string templateId) =>
_repository.SetLastOpenedAsync(templateId);
public ProgressionSystem(ProgressionRepository repository)
{

View File

@@ -4,7 +4,7 @@ using UnityEngine;
namespace Darkmatter.Features.ShapeBuilder.Commands
{
internal sealed class SnapPieceCommand : ICommand
public sealed class SnapPieceCommand : ICommand
{
private readonly ShapePiece _piece;
private readonly Vector2 _prevPos;

View File

@@ -1,6 +1,7 @@
using Darkmatter.Core.Contracts.Features.ShapeBuilder;
using Darkmatter.Core.Data.Static.Features.ShapeBuilder;
using Darkmatter.Features.ShapeBuilder.Systems;
using Darkmatter.Features.ShapeBuilder.UI;
using Darkmatter.Libs.Installers;
using UnityEngine;
using VContainer;
@@ -11,11 +12,20 @@ namespace Darkmatter.Features.ShapeBuilder.Installers
public class ShapeBuilderFeatureModule : MonoBehaviour, IModule
{
[SerializeField] private ShapeBuilderConfig config;
[SerializeField] private ShapeHolderView holderView;
public void Register(IContainerBuilder builder)
{
if (config != null)
builder.RegisterComponent(config);
if (holderView != null)
{
builder.RegisterComponent(holderView);
builder.RegisterEntryPoint<ShapeHolderPresenter>().WithParameter(holderView);
}
builder.Register<IShapePieceFactory, ShapePieceFactory>(Lifetime.Singleton);
builder.Register<IShapeBuilderController, ShapeBuilderController>(Lifetime.Singleton);
}
}

View File

@@ -0,0 +1,11 @@
using Darkmatter.Core.Data.Static.Features.ShapeBuilder;
using Darkmatter.Features.ShapeBuilder.UI;
using UnityEngine;
namespace Darkmatter.Features.ShapeBuilder.Systems
{
public interface IShapePieceFactory
{
ShapePiece Create(GameObject prefab, ShapeSO shape, SlotMarker slot, Vector2 trayPos);
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 7eac815466c2c48b3b37bede20218b99

View File

@@ -4,13 +4,13 @@ using System.Linq;
using System.Threading;
using Cysharp.Threading.Tasks;
using Darkmatter.Core.Contracts.Features.DrawingCatalog;
using Darkmatter.Core.Contracts.Features.GameplayFlow;
using Darkmatter.Core.Contracts.Features.History;
using Darkmatter.Core.Contracts.Features.ShapeBuilder;
using Darkmatter.Core.Contracts.Services.Assets;
using Darkmatter.Core.Contracts.Services.Audio;
using Darkmatter.Core.Contracts.Features.GameplayFlow;
using Darkmatter.Core.Data.Signals.Features.ShapeBuilder;
using Darkmatter.Core.Data.Static.Features.ShapeBuilder;
using Darkmatter.Features.ShapeBuilder.Commands;
using Darkmatter.Features.ShapeBuilder.UI;
using Darkmatter.Libs.Observer;
using UnityEngine;
@@ -28,48 +28,58 @@ namespace Darkmatter.Features.ShapeBuilder.Systems
private GameObject _drawingInstance;
private GameObject _piecePrefab;
private IDisposable _snappedSub;
private IDisposable _unsnappedSub;
private readonly List<string> _snappedPieceIds = new();
private readonly ShapeBuilderConfig _cfg;
private readonly ISfxPlayer _sfx;
private readonly List<ShapePiece> _pieces = new();
private readonly IEventBus _bus;
private readonly IUndoStack _undo;
private readonly IAssetProviderService _assetProviderService;
private readonly IEventBus _eventBus;
private readonly IShapePieceFactory _factory;
private readonly ShapeHolderView _holder;
private readonly IGameplaySceneRefs _refs;
private readonly IUndoStack _undo;
public ShapeBuilderController(
ShapeBuilderConfig cfg,
ISfxPlayer sfx,
IEventBus bus,
IUndoStack undo,
IAssetProviderService assetProviderService,
IEventBus eventBus,
IGameplaySceneRefs refs)
IShapePieceFactory factory,
ShapeHolderView holder,
IGameplaySceneRefs refs,
IUndoStack undo)
{
_cfg = cfg;
_sfx = sfx;
_bus = bus;
_undo = undo;
_assetProviderService = assetProviderService;
_eventBus = eventBus;
_factory = factory;
_holder = holder;
_refs = refs;
_undo = undo;
}
public async UniTask InitializeAsync(CancellationToken cancellationToken)
{
await TryLoadPiecePrefabAsync(cancellationToken);
cancellationToken.ThrowIfCancellationRequested();
_snappedSub = _eventBus.Subscribe<PieceSnappedSignal>(OnPieceSnapped);
_snappedSub?.Dispose();
_unsnappedSub?.Dispose();
_snappedSub = _bus.Subscribe<PieceSnappedSignal>(OnPieceSnapped);
_unsnappedSub = _bus.Subscribe<PieceUnsnappedSignal>(OnPieceUnsnapped);
}
private void OnPieceSnapped(PieceSnappedSignal obj)
{
_snapped++;
_snappedPieceIds.Add(obj.PieceId);
CheckIfShapeAssembled();
}
private void OnPieceUnsnapped(PieceUnsnappedSignal obj)
{
_snapped = Mathf.Max(0, _snapped - 1);
_snappedPieceIds.Remove(obj.PieceId);
}
public async UniTask BuildAsync(
IDrawingTemplate template,
IReadOnlyCollection<string> preSnappedIds,
@@ -77,24 +87,27 @@ namespace Darkmatter.Features.ShapeBuilder.Systems
{
Clear();
if (template.Prefab == null)
if (template.DrawingPrefab == null)
throw new System.Exception($"No drawing layout prefab for '{template.Id}'");
_drawingInstance = Object.Instantiate(template.Prefab, _refs.PaperRoot);
_drawingInstance = Object.Instantiate(template.DrawingPrefab, _refs.PaperRoot);
ct.ThrowIfCancellationRequested();
var slots = _drawingInstance.GetComponentsInChildren<SlotMarker>(includeInactive: true);
await TryLoadPiecePrefabAsync(ct);
ct.ThrowIfCancellationRequested();
int count = template.Pieces.Count;
float trayW = _refs.TrayPanel.rect.width;
float trayW = _holder.SpawnWidth;
float pitch = trayW / (count + 1);
_currentTemplateId = template.Id;
_expected = count;
_snapped = 0;
_bus.Publish(new ShapeBuilderStartedSignal(template.Id));
CreateShapePieceInstances(template, preSnappedIds, count, slots, pitch, trayW);
CheckIfShapeAssembled();
@@ -117,13 +130,16 @@ namespace Darkmatter.Features.ShapeBuilder.Systems
var trayPos = new Vector2(pitch * (i + 1) - trayW * 0.5f, 0f);
bool preSnapped = preSnappedIds != null && preSnappedIds.Contains(shape.Id);
var go = Object.Instantiate(_piecePrefab, _refs.TrayPanel);
go.name = $"Piece_{shape.Id}";
var piece = _factory.Create(_piecePrefab, shape, slot, trayPos);
_pieces.Add(piece);
var piece = go.GetComponent<ShapePiece>();
piece.Setup(shape, slot, _cfg, _sfx, _bus, _undo, trayPos, preSnapped);
if (preSnapped) _snapped++;
if (preSnapped)
{
_undo.Append(new SnapPieceCommand(piece));
piece.SnapInstantly();
_snapped++;
_snappedPieceIds.Add(shape.Id);
}
}
}
@@ -159,11 +175,25 @@ namespace Darkmatter.Features.ShapeBuilder.Systems
public void Clear()
{
_undo.Drop();
foreach (var piece in _pieces)
if (piece != null) Object.Destroy(piece.gameObject);
_pieces.Clear();
if (_drawingInstance != null) Object.Destroy(_drawingInstance);
_drawingInstance = null;
_snappedPieceIds.Clear();
_snapped = 0;
_expected = 0;
_currentTemplateId = null;
}
public void Dispose()
{
_snappedSub?.Dispose();
_unsnappedSub?.Dispose();
}
}
}

View File

@@ -0,0 +1,42 @@
using Darkmatter.Core.Contracts.Features.History;
using Darkmatter.Core.Contracts.Services.Audio;
using Darkmatter.Core.Data.Static.Features.ShapeBuilder;
using Darkmatter.Features.ShapeBuilder.UI;
using Darkmatter.Libs.Observer;
using UnityEngine;
namespace Darkmatter.Features.ShapeBuilder.Systems
{
public sealed class ShapePieceFactory : IShapePieceFactory
{
private readonly ShapeHolderView _holder;
private readonly ShapeBuilderConfig _cfg;
private readonly ISfxPlayer _sfx;
private readonly IEventBus _bus;
private readonly IUndoStack _undo;
public ShapePieceFactory(
ShapeHolderView holder,
ShapeBuilderConfig cfg,
ISfxPlayer sfx,
IEventBus bus,
IUndoStack undo)
{
_holder = holder;
_cfg = cfg;
_sfx = sfx;
_bus = bus;
_undo = undo;
}
public ShapePiece Create(GameObject prefab, ShapeSO shape, SlotMarker slot, Vector2 trayPos)
{
var go = Object.Instantiate(prefab, _holder.SpawnRoot);
go.name = $"Piece_{shape.Id}";
var piece = go.GetComponent<ShapePiece>();
piece.Setup(shape, slot, _cfg, _sfx, _bus, _undo, trayPos);
return piece;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 34f1057e9a8774bbbaf0aac5ac1ae1a8

View File

@@ -0,0 +1,35 @@
using System;
using Darkmatter.Core.Data.Signals.Features.ShapeBuilder;
using Darkmatter.Libs.Observer;
using VContainer.Unity;
namespace Darkmatter.Features.ShapeBuilder.UI
{
public class ShapeHolderPresenter : IStartable, IDisposable
{
private readonly ShapeHolderView _view;
private readonly IEventBus _bus;
private IDisposable _startedSub;
private IDisposable _assembledSub;
public ShapeHolderPresenter(ShapeHolderView view, IEventBus bus)
{
_view = view;
_bus = bus;
}
public void Start()
{
_view.HideInstant();
_startedSub = _bus.Subscribe<ShapeBuilderStartedSignal>(_ => _view.Show());
_assembledSub = _bus.Subscribe<ShapeAssembledSignal>(_ => _view.Hide());
}
public void Dispose()
{
_startedSub?.Dispose();
_assembledSub?.Dispose();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: bdc42ff3cf54249eba34beb822f4e9fb

View File

@@ -0,0 +1,77 @@
using PrimeTween;
using UnityEngine;
namespace Darkmatter.Features.ShapeBuilder.UI
{
[RequireComponent(typeof(CanvasGroup))]
public class ShapeHolderView : MonoBehaviour
{
[SerializeField] private RectTransform spawnRoot;
[SerializeField] private RectTransform animatedRoot;
[SerializeField] private float showDuration = 0.35f;
[SerializeField] private float hideDuration = 0.25f;
[SerializeField] private Vector2 hiddenOffset = new(1500f, 0f);
private CanvasGroup _canvasGroup;
private Vector2 _shownAnchoredPos;
private Sequence _activeSequence;
public RectTransform SpawnRoot => spawnRoot;
public float SpawnWidth => spawnRoot.rect.width;
private void Awake()
{
_canvasGroup = GetComponent<CanvasGroup>();
if (animatedRoot == null) animatedRoot = (RectTransform)transform;
_shownAnchoredPos = animatedRoot.anchoredPosition;
}
public Sequence Show()
{
KillActive();
gameObject.SetActive(true);
_canvasGroup.interactable = true;
_canvasGroup.blocksRaycasts = true;
_activeSequence = Sequence.Create()
.Group(Tween.UIAnchoredPosition(animatedRoot, _shownAnchoredPos, showDuration, Ease.OutBack))
.Group(Tween.Alpha(_canvasGroup, 1f, showDuration, Ease.OutQuad));
return _activeSequence;
}
public Sequence Hide()
{
KillActive();
_canvasGroup.interactable = false;
_canvasGroup.blocksRaycasts = false;
var hiddenPos = _shownAnchoredPos + hiddenOffset;
_activeSequence = Sequence.Create()
.Group(Tween.UIAnchoredPosition(animatedRoot, hiddenPos, hideDuration, Ease.InQuad))
.Group(Tween.Alpha(_canvasGroup, 0f, hideDuration, Ease.InQuad))
.ChainCallback(() => gameObject.SetActive(false));
return _activeSequence;
}
public void HideInstant()
{
KillActive();
animatedRoot.anchoredPosition = _shownAnchoredPos + hiddenOffset;
_canvasGroup.alpha = 0f;
_canvasGroup.interactable = false;
_canvasGroup.blocksRaycasts = false;
gameObject.SetActive(false);
}
private void KillActive()
{
if (_activeSequence.isAlive) _activeSequence.Stop();
_activeSequence = default;
}
private void OnDisable()
{
KillActive();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 7763b07d532494bda8fa4cc10344dbd6

View File

@@ -48,8 +48,7 @@ namespace Darkmatter.Features.ShapeBuilder.UI
ISfxPlayer sfx,
IEventBus bus,
IUndoStack undo,
Vector2 trayPos,
bool preSnapped)
Vector2 trayPos)
{
_shape = shape;
_slot = slot;
@@ -62,8 +61,6 @@ namespace Darkmatter.Features.ShapeBuilder.UI
image.sprite = shape.Sprite;
ApplyTrayPose();
if (preSnapped) SnapInstantly();
}
public void OnBeginDrag(PointerEventData e)
@@ -154,7 +151,7 @@ namespace Darkmatter.Features.ShapeBuilder.UI
_bus.Publish(new PieceUnsnappedSignal(_shape.Id));
}
private void SnapInstantly()
public void SnapInstantly()
{
Lock();
var slot = _slot.RectTransform;
@@ -192,5 +189,12 @@ namespace Darkmatter.Features.ShapeBuilder.UI
_parentRect, screenPos, _eventCam, out var local);
return local;
}
private void OnDestroy()
{
// Kill any tweens that were targeting this piece's RectTransform so
// PrimeTween doesn't keep writing into a destroyed transform.
Tween.StopAll(onTarget: RectTransform);
}
}
}

View File

@@ -119,6 +119,19 @@ TextureImporter:
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: iOS
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []

View File

@@ -119,6 +119,19 @@ TextureImporter:
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: iOS
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []

View File

@@ -67,7 +67,7 @@ TextureImporter:
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
@@ -80,7 +80,7 @@ TextureImporter:
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
@@ -93,7 +93,7 @@ TextureImporter:
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
- serializedVersion: 4
buildTarget: WebGL
maxTextureSize: 2048
resizeAlgorithm: 0
@@ -106,7 +106,7 @@ TextureImporter:
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
- serializedVersion: 4
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
@@ -123,6 +123,7 @@ TextureImporter:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
@@ -132,6 +133,8 @@ TextureImporter:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0

View File

@@ -119,6 +119,19 @@ TextureImporter:
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: iOS
maxTextureSize: 512
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []

View File

@@ -119,6 +119,19 @@ TextureImporter:
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: iOS
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []

View File

@@ -119,6 +119,19 @@ TextureImporter:
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: iOS
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []

View File

@@ -119,6 +119,19 @@ TextureImporter:
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: iOS
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []

View File

@@ -119,6 +119,19 @@ TextureImporter:
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: iOS
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []

View File

@@ -119,6 +119,19 @@ TextureImporter:
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: iOS
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []

View File

@@ -119,6 +119,19 @@ TextureImporter:
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: iOS
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []

View File

@@ -119,6 +119,19 @@ TextureImporter:
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: iOS
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []

View File

@@ -119,6 +119,19 @@ TextureImporter:
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: iOS
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []

View File

@@ -119,6 +119,19 @@ TextureImporter:
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: iOS
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []

View File

@@ -119,6 +119,19 @@ TextureImporter:
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: iOS
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []

View File

@@ -119,6 +119,19 @@ TextureImporter:
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: iOS
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []

View File

@@ -119,6 +119,19 @@ TextureImporter:
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: iOS
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []

Some files were not shown because too many files have changed in this diff Show More