Compare commits
29 Commits
e96fefa642
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 378b00096a | |||
|
|
6d9b096e8e | ||
|
|
919169157e | ||
| 96e69c3d1a | |||
|
|
a51a73efdd | ||
|
|
3914adaa84 | ||
|
|
4e8f2ae7ad | ||
| bdac5b485f | |||
|
|
b3c096a150 | ||
|
|
9899a8b549 | ||
|
|
820f614415 | ||
| 92aa1d2751 | |||
|
|
9ac752b23c | ||
| 252c1b8a41 | |||
|
|
f9a2532495 | ||
|
|
e6b6683feb | ||
| b16f535499 | |||
|
|
9ad3c34099 | ||
|
|
09ad3469f2 | ||
|
|
98fbad9233 | ||
| 380e8b63bd | |||
|
|
d239dbd9b5 | ||
|
|
3f14d0b346 | ||
|
|
86bf52ced4 | ||
|
|
4064df19bc | ||
|
|
01ec3bec54 | ||
|
|
f1f3a35c6d | ||
|
|
84fea79158 | ||
|
|
e5b63e158c |
@@ -20,6 +20,11 @@ MonoBehaviour:
|
||||
m_ReadOnly: 0
|
||||
m_SerializedLabels: []
|
||||
FlaggedDuringContentUpdateRestriction: 0
|
||||
- m_GUID: 145ae55f6571bfe4fbadaefb863ba69d
|
||||
m_Address: Colorbook
|
||||
m_ReadOnly: 0
|
||||
m_SerializedLabels: []
|
||||
FlaggedDuringContentUpdateRestriction: 0
|
||||
- m_GUID: e5f73f24e812e4a98b4c17d533fd3d6d
|
||||
m_Address: Gameplay
|
||||
m_ReadOnly: 0
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
using Darkmatter.App.LifetimeScopes;
|
||||
|
||||
public class ColorBookLifetimeScope : BaseLifetimeScope
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8bbdb0f5cdf25c34086f816c59836c9d
|
||||
@@ -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();
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b5ba5a7108234751ac286a57795fac06
|
||||
timeCreated: 1779971202
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fee5907f5643d492ab8ba177f84c30f6
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aa96e836f66d5412a8ea6c3ffb0128b1
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Darkmatter.Core.Contracts.Features.Loading;
|
||||
|
||||
public interface ILoadingScreen
|
||||
{
|
||||
float CurrentProgress { get; }
|
||||
void Show();
|
||||
void Hide();
|
||||
void SetProgress(float progress);
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 95b8a017648e4b2ba45750c4a5c8bc5f
|
||||
timeCreated: 1771491592
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -7,5 +7,6 @@ namespace Darkmatter.Core.Data.Dynamic.Features.Progression
|
||||
public struct ProgressionRootDto
|
||||
{
|
||||
public List<DrawingProgress> records;
|
||||
public string lastOpenedTemplateId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Darkmatter.Core
|
||||
{
|
||||
public record struct OpenArtBookSignal;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cf8582ced90151445a9fa7b610f59ce9
|
||||
@@ -0,0 +1,6 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Darkmatter.Core
|
||||
{
|
||||
public record struct OpenColorBookSignal;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 73bddc8d591de2a438044c2a9ee180be
|
||||
@@ -0,0 +1,7 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Darkmatter.Core
|
||||
{
|
||||
public record struct ReturnToMainMenuSignal;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fdd1f5bd7186d6244b2311e9f5b18a95
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4c2af4ea812e45de8f189ce182b2a22e
|
||||
timeCreated: 1779963993
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace Darkmatter.Core.Data.Signals.Features.MainMenu;
|
||||
|
||||
public record struct PlayBtnClickedSignal();
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3dfe3ef5d7da4861becbc8791ce7c852
|
||||
timeCreated: 1779964014
|
||||
@@ -0,0 +1,4 @@
|
||||
namespace Darkmatter.Core.Data.Signals.Features.ShapeBuilder
|
||||
{
|
||||
public record struct ShapeBuilderStartedSignal(string TemplateId);
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 40f2733e19ad34fe5ba5d63aa2e9a39b
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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)")]
|
||||
|
||||
@@ -6,5 +6,6 @@ namespace Darkmatter.Core.Enums.Services.Audio
|
||||
ShapeHover = 100,
|
||||
ShapeSnap = 101,
|
||||
ShapeReturn = 102,
|
||||
UiTap = 200,
|
||||
}
|
||||
}
|
||||
|
||||
8
Assets/Darkmatter/Code/Features/ArtBook.meta
Normal file
8
Assets/Darkmatter/Code/Features/ArtBook.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5d8eacd004e3e0649b6ff4a6b58c952c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dd1597db9436d6f4da32f1391b905651
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Darkmatter/Code/Features/ArtBook/Installer.meta
Normal file
8
Assets/Darkmatter/Code/Features/ArtBook/Installer.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: af74b0a63b3e4a746885f0e4b911cd91
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e42bea53c6fa23c45a7c85e9b025bed7
|
||||
8
Assets/Darkmatter/Code/Features/ArtBook/UI.meta
Normal file
8
Assets/Darkmatter/Code/Features/ArtBook/UI.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4a13b92fe0335e04c8de0c5cbc54f6fb
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,7 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Darkmatter.Features.Artbook
|
||||
{
|
||||
public record struct ArtbookEntry(string Id, string Name, Texture2D Thumbnail, DateTime UpdatedUtc);
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6821df5d7eb2fe345aeeebd1290d84b3
|
||||
201
Assets/Darkmatter/Code/Features/ArtBook/UI/ArtbookPresenter.cs
Normal file
201
Assets/Darkmatter/Code/Features/ArtBook/UI/ArtbookPresenter.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ce45172769c70e044a56095bc9da281a
|
||||
92
Assets/Darkmatter/Code/Features/ArtBook/UI/ArtbookView.cs
Normal file
92
Assets/Darkmatter/Code/Features/ArtBook/UI/ArtbookView.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d635629f252db53479e66347a37c4f0b
|
||||
@@ -4,8 +4,11 @@
|
||||
"references": [
|
||||
"GUID:6a0a834eb41764f12ba55c3fb04a40cb",
|
||||
"GUID:c1c03c0e5b2f4412b9f2be1c20d6a9b1",
|
||||
"GUID:c176ee863a5e74e88a6517f9f102cf92",
|
||||
"GUID:b4c9f7fbf1e144933a1797dc208ece5f",
|
||||
"GUID:b0214a6008ed146ff8f122a6a9c2f6cc",
|
||||
"GUID:f51ebe6a0ceec4240a699833d6309b23"
|
||||
"GUID:f51ebe6a0ceec4240a699833d6309b23",
|
||||
"GUID:80ecb87cae9c44d19824e70ea7229748"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
@@ -16,4 +19,4 @@
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Darkmatter.Features.Colorbook.Installers
|
||||
{
|
||||
public void Register(IContainerBuilder builder)
|
||||
{
|
||||
builder.RegisterEntryPoint<ColorbookFlowController>(Lifetime.Singleton);
|
||||
builder.RegisterEntryPoint<ColorbookFlowController>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +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;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
8
Assets/Darkmatter/Code/Features/Coloring/Commands.meta
Normal file
8
Assets/Darkmatter/Code/Features/Coloring/Commands.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 02004f7c20c0640778c41a7643c2da82
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c212dc9aaa7e4445968fee229a715b3b
|
||||
timeCreated: 1779972906
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
8
Assets/Darkmatter/Code/Features/Coloring/Systems.meta
Normal file
8
Assets/Darkmatter/Code/Features/Coloring/Systems.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 569e45483f54348a593b6571740cd8a2
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1bba9a928a76471391068acd46c4f684
|
||||
timeCreated: 1779970637
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0849f8cc757842278c38ee6f4d61b985
|
||||
timeCreated: 1779971175
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a288824e4f554000994bec451c09957c
|
||||
timeCreated: 1779970861
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 91b71be7f45a41cbb17492f82a3f0967
|
||||
timeCreated: 1779970564
|
||||
8
Assets/Darkmatter/Code/Features/Coloring/UI.meta
Normal file
8
Assets/Darkmatter/Code/Features/Coloring/UI.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 416f96737010f40c085ac404743fa8ef
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
54
Assets/Darkmatter/Code/Features/Coloring/UI/ColorButton.cs
Normal file
54
Assets/Darkmatter/Code/Features/Coloring/UI/ColorButton.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7e64b829b90144faa3bd2c7f5c0ad291
|
||||
timeCreated: 1779970210
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cc0590d8f6fce4487943575bfb96c32a
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c960fbf3dc5a54f5399178665d20b1ce
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7667901d8aea645d28b5125a2ed546ce
|
||||
@@ -2,6 +2,7 @@ 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.Data.Signals.Features.Drawing;
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Darkmatter.Features.DrawingCatalog;
|
||||
|
||||
public class DrawingCatalogButton : MonoBehaviour
|
||||
namespace Darkmatter.Features.DrawingCatalog
|
||||
{
|
||||
public string Id { get; private set; }
|
||||
[SerializeField] private Image thumbnail;
|
||||
[SerializeField] private Button button;
|
||||
|
||||
public void Initialize(string id,Sprite thumbnailSprite, UnityEngine.Events.UnityAction onClick)
|
||||
public class DrawingCatalogButton : MonoBehaviour
|
||||
{
|
||||
Id = id;
|
||||
thumbnail.sprite = thumbnailSprite;
|
||||
button.onClick.AddListener(onClick);
|
||||
public string Id { get; private set; }
|
||||
[SerializeField] private Image thumbnail;
|
||||
[SerializeField] private Button button;
|
||||
|
||||
public void Initialize(string id, Sprite thumbnailSprite, UnityEngine.Events.UnityAction onClick)
|
||||
{
|
||||
Id = id;
|
||||
thumbnail.sprite = thumbnailSprite;
|
||||
button.onClick.AddListener(onClick);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,31 +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.Features.DrawingCatalog.Systems;
|
||||
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, DrawingCatalogController controller,
|
||||
IDrawingTemplateCatalog catalog)
|
||||
public DrawingCatalogPresenter(DrawingCatalogView view, IDrawingCatalogController controller,
|
||||
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()
|
||||
{
|
||||
_eventBus.Publish(new OpenArtBookSignal());
|
||||
_view.Hide();
|
||||
}
|
||||
|
||||
private void OnBackBtnClicked()
|
||||
{
|
||||
_eventBus.Publish(new ReturnToMainMenuSignal());
|
||||
Debug.Log("Back button clicked in Drawing Catalog");
|
||||
}
|
||||
|
||||
private void OnItemClicked(string id) =>
|
||||
@@ -39,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;
|
||||
@@ -47,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();
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Darkmatter.Libs.Observer;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Pool;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Darkmatter.Features.DrawingCatalog
|
||||
{
|
||||
@@ -9,10 +11,41 @@ namespace Darkmatter.Features.DrawingCatalog
|
||||
{
|
||||
[SerializeField] private RectTransform content;
|
||||
[SerializeField] private DrawingCatalogButton buttonPrefab;
|
||||
|
||||
[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)
|
||||
{
|
||||
while (_buttons.Count < items.Count)
|
||||
@@ -30,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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": [],
|
||||
@@ -15,4 +19,4 @@
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ef06e7bcb7ee346d894886c4a4848bcd
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 54c118b4507054746befcaa85a1e9578
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
8
Assets/Darkmatter/Code/Features/Loading.meta
Normal file
8
Assets/Darkmatter/Code/Features/Loading.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 09a530ea6231c44aeb08c880de69eeda
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Darkmatter/Code/Features/Loading/Docs.meta
Normal file
8
Assets/Darkmatter/Code/Features/Loading/Docs.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7b3ba9e51a74d4a0f8673b21a9124298
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,17 @@
|
||||
# Loading Feature
|
||||
|
||||
## Purpose
|
||||
- Owns the passive loading-screen presenter/view pair shared by boot and scene transitions.
|
||||
- Centralizes progress and status updates for the user-facing loading UI.
|
||||
|
||||
## Public Entry Points
|
||||
- `LoadingPresenter`
|
||||
- `LoadingScreenView`
|
||||
|
||||
## Dependencies
|
||||
- Consumed from App/loading orchestration via the loading contracts.
|
||||
- Should remain presentation-only and avoid feature-specific gameplay logic.
|
||||
|
||||
## Extension Notes
|
||||
- Add new UI affordances here only if they are generic to every loading flow.
|
||||
- Keep orchestration decisions in App/GameFlow/MainMenu sequences, not in the presenter or view.
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 14e78c2457d7941859fa8192ae052e72
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "Features.Loading",
|
||||
"rootNamespace": "Darkmatter.Features.Loading",
|
||||
"references": [
|
||||
"GUID:6a0a834eb41764f12ba55c3fb04a40cb",
|
||||
"GUID:c1c03c0e5b2f4412b9f2be1c20d6a9b1",
|
||||
"GUID:f51ebe6a0ceec4240a699833d6309b23",
|
||||
"GUID:b0214a6008ed146ff8f122a6a9c2f6cc",
|
||||
"GUID:6055be8ebefd69e48b49212b09b47b2f",
|
||||
"GUID:80ecb87cae9c44d19824e70ea7229748"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b6b16414554114d0b971c01ee2f137fb
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Darkmatter/Code/Features/Loading/Installers.meta
Normal file
8
Assets/Darkmatter/Code/Features/Loading/Installers.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1d5d2ca79d314453cba062c16dce8181
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,16 @@
|
||||
using Darkmatter.Libs.Installers;
|
||||
using UnityEngine;
|
||||
using VContainer;
|
||||
using VContainer.Unity;
|
||||
|
||||
namespace Darkmatter.Features.Loading
|
||||
{
|
||||
public class LoadingFeatureModule : MonoBehaviour,IModule
|
||||
{
|
||||
[SerializeField] private LoadingScreenView loadingScreenView;
|
||||
public void Register(IContainerBuilder builder)
|
||||
{
|
||||
builder.RegisterEntryPoint<LoadingPresenter>().WithParameter(loadingScreenView);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 673a21bf7c6bb430eadece2a989d79ac
|
||||
8
Assets/Darkmatter/Code/Features/Loading/UI.meta
Normal file
8
Assets/Darkmatter/Code/Features/Loading/UI.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 43e5eadd1991e4cbcb672bf0fd4dfde9
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
110
Assets/Darkmatter/Code/Features/Loading/UI/LoadingPresenter.cs
Normal file
110
Assets/Darkmatter/Code/Features/Loading/UI/LoadingPresenter.cs
Normal file
@@ -0,0 +1,110 @@
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Darkmatter.Core.Contracts.Features.Loading;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Darkmatter.Features.Loading
|
||||
{
|
||||
public class LoadingPresenter : ILoadingScreen
|
||||
{
|
||||
private const float SnapToCompleteThreshold = 0.9999f;
|
||||
private const float ProgressEpsilon = 0.0001f;
|
||||
|
||||
private readonly LoadingScreenView _view;
|
||||
private float _reportedProgress;
|
||||
private float _displayedProgress;
|
||||
private bool _isVisible;
|
||||
private bool _isAnimating;
|
||||
|
||||
public float CurrentProgress => _reportedProgress;
|
||||
|
||||
public LoadingPresenter(LoadingScreenView view)
|
||||
{
|
||||
_view = view;
|
||||
}
|
||||
|
||||
public void Show()
|
||||
{
|
||||
if (_isVisible)
|
||||
{
|
||||
_view.Show();
|
||||
return;
|
||||
}
|
||||
|
||||
_reportedProgress = 0f;
|
||||
_displayedProgress = 0f;
|
||||
_view.SetProgress(0f);
|
||||
_view.Show();
|
||||
_isVisible = true;
|
||||
}
|
||||
|
||||
public void Hide()
|
||||
{
|
||||
if (_reportedProgress >= SnapToCompleteThreshold)
|
||||
{
|
||||
SnapDisplayedProgress(1f);
|
||||
}
|
||||
|
||||
_isVisible = false;
|
||||
_view.Hide();
|
||||
}
|
||||
|
||||
public void SetProgress(float progress)
|
||||
{
|
||||
progress = Mathf.Clamp01(progress);
|
||||
progress = Mathf.Max(_reportedProgress, progress);
|
||||
|
||||
if (progress >= SnapToCompleteThreshold)
|
||||
{
|
||||
_reportedProgress = 1f;
|
||||
SnapDisplayedProgress(1f);
|
||||
return;
|
||||
}
|
||||
|
||||
_reportedProgress = progress;
|
||||
if (!_isVisible)
|
||||
{
|
||||
SnapDisplayedProgress(progress);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_displayedProgress + ProgressEpsilon >= _reportedProgress || _isAnimating)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AnimateProgressAsync().Forget();
|
||||
}
|
||||
|
||||
private async UniTaskVoid AnimateProgressAsync()
|
||||
{
|
||||
_isAnimating = true;
|
||||
try
|
||||
{
|
||||
while (_isVisible && _displayedProgress + ProgressEpsilon < _reportedProgress)
|
||||
{
|
||||
var deltaTime = Mathf.Max(Time.unscaledDeltaTime, 1f / 60f);
|
||||
var smoothing = 1f - Mathf.Exp(-_view.LoadingSmoothTime * deltaTime);
|
||||
_displayedProgress = Mathf.Lerp(_displayedProgress, _reportedProgress, smoothing);
|
||||
|
||||
if (_reportedProgress - _displayedProgress <= 0.001f)
|
||||
{
|
||||
_displayedProgress = _reportedProgress;
|
||||
}
|
||||
|
||||
_view.SetProgress(_displayedProgress);
|
||||
await UniTask.Yield(PlayerLoopTiming.Update);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isAnimating = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void SnapDisplayedProgress(float progress)
|
||||
{
|
||||
_displayedProgress = progress;
|
||||
_view.SetProgress(progress);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 00b0a1507e1d4b27b44e77c4967aed99
|
||||
timeCreated: 1771494975
|
||||
@@ -0,0 +1,85 @@
|
||||
using PrimeTween;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Darkmatter.Features.Loading
|
||||
{
|
||||
public class LoadingScreenView : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private Slider loadingSlider;
|
||||
[SerializeField] private TMP_Text statusText;
|
||||
[SerializeField] private float loadingSmoothTime = 10f;
|
||||
[SerializeField] private float dotInterval = 0.4f;
|
||||
public float LoadingSmoothTime => loadingSmoothTime;
|
||||
|
||||
private static readonly string[] StatusLines = {
|
||||
"Blue is arguing with red",
|
||||
"The crayons are dancing",
|
||||
"Yellow spilled everywhere",
|
||||
"Chasing escaped paint drops",
|
||||
"Untying rainbow knots",
|
||||
"Cleaning glitter explosions",
|
||||
"The color monsters are hungry",
|
||||
"Mixing mystery colors",
|
||||
"Someone ate the purple paint",
|
||||
"Teaching green to behave"
|
||||
};
|
||||
|
||||
private static readonly string[] DotFrames = { ".", "..", "..." };
|
||||
|
||||
private string _baseLine;
|
||||
private int _dotIndex;
|
||||
private Sequence _dotSequence;
|
||||
|
||||
public void Show()
|
||||
{
|
||||
gameObject.SetActive(true);
|
||||
|
||||
if (statusText == null || StatusLines.Length == 0) return;
|
||||
|
||||
_baseLine = StatusLines[Random.Range(0, StatusLines.Length)];
|
||||
_dotIndex = 0;
|
||||
statusText.text = _baseLine + DotFrames[_dotIndex];
|
||||
|
||||
StartDotAnimation();
|
||||
}
|
||||
|
||||
public void Hide()
|
||||
{
|
||||
StopDotAnimation();
|
||||
gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
public void SetProgress(float progress)
|
||||
{
|
||||
if (loadingSlider != null) loadingSlider.value = progress;
|
||||
}
|
||||
|
||||
private void StartDotAnimation()
|
||||
{
|
||||
StopDotAnimation();
|
||||
_dotSequence = Sequence.Create(useUnscaledTime: true, cycles: -1)
|
||||
.ChainDelay(dotInterval)
|
||||
.ChainCallback(AdvanceDots);
|
||||
}
|
||||
|
||||
private void StopDotAnimation()
|
||||
{
|
||||
if (_dotSequence.isAlive) _dotSequence.Stop();
|
||||
_dotSequence = default;
|
||||
}
|
||||
|
||||
private void AdvanceDots()
|
||||
{
|
||||
if (statusText == null || string.IsNullOrEmpty(_baseLine)) return;
|
||||
_dotIndex = (_dotIndex + 1) % DotFrames.Length;
|
||||
statusText.text = _baseLine + DotFrames[_dotIndex];
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
StopDotAnimation();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 48d729b720c548e685793cc0348ae1c5
|
||||
timeCreated: 1771494930
|
||||
@@ -7,7 +7,8 @@
|
||||
"GUID:b0214a6008ed146ff8f122a6a9c2f6cc",
|
||||
"GUID:68765d262e2128e4ab49c983f3411946",
|
||||
"GUID:80ecb87cae9c44d19824e70ea7229748",
|
||||
"GUID:f51ebe6a0ceec4240a699833d6309b23"
|
||||
"GUID:f51ebe6a0ceec4240a699833d6309b23",
|
||||
"GUID:b4c9f7fbf1e144933a1797dc208ece5f"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
using System;
|
||||
using Darkmatter.Core.Data.Signals.Features.AppBoot;
|
||||
using Darkmatter.Core.Data.Signals.Features.MainMenu;
|
||||
using Darkmatter.Libs.Observer;
|
||||
using UnityEngine;
|
||||
using VContainer.Unity;
|
||||
|
||||
@@ -6,21 +9,31 @@ namespace Darkmatter.Features.Mainmenu
|
||||
{
|
||||
public class MainMenuPresenter : IStartable, IDisposable
|
||||
{
|
||||
private readonly IEventBus _eventBus;
|
||||
private readonly MainMenuView _view;
|
||||
|
||||
public MainMenuPresenter(MainMenuView view)
|
||||
|
||||
|
||||
public MainMenuPresenter(MainMenuView view, IEventBus eventBus)
|
||||
{
|
||||
_view = view;
|
||||
_eventBus = eventBus;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
_view.OnPlayBtnClickedEvent += OnPlayBtnClicked;
|
||||
_eventBus.Subscribe<IntroCompletedSignal>(OnIntroComplete);
|
||||
}
|
||||
|
||||
private void OnIntroComplete(IntroCompletedSignal signal)
|
||||
{
|
||||
_view.PlayMascotIntro();
|
||||
}
|
||||
|
||||
private void OnPlayBtnClicked()
|
||||
{
|
||||
Debug.Log("Play Btn Clicked");
|
||||
_eventBus.Publish(new PlayBtnClickedSignal());
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
||||
@@ -10,10 +10,8 @@ namespace Darkmatter.Features.Mainmenu
|
||||
public class MainMenuView : MonoBehaviour
|
||||
{
|
||||
[Header("UI Elements")]
|
||||
[SerializeField] private Button playBtn;
|
||||
[SerializeField] private ParticleSystem playBtnParticle;
|
||||
[SerializeField] private Animator playBtnAnimator;
|
||||
|
||||
//[SerializeField] private Button playBtn;
|
||||
[SerializeField] private PlayButtonView playBtn;
|
||||
[SerializeField] private SkeletonGraphic mascotSkeletonGraphic;
|
||||
|
||||
[Header("Mascot Animations")]
|
||||
@@ -28,8 +26,7 @@ namespace Darkmatter.Features.Mainmenu
|
||||
|
||||
private void Start()
|
||||
{
|
||||
playBtn.onClick.AddListener(OnPlayBtnClicked);
|
||||
PlayMascotIntro();
|
||||
playBtn.OnPlayBtnClickedEvent += () => OnPlayBtnClickedEvent?.Invoke();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
@@ -39,7 +36,7 @@ namespace Darkmatter.Features.Mainmenu
|
||||
helloCts = null;
|
||||
}
|
||||
|
||||
private void PlayMascotIntro()
|
||||
public void PlayMascotIntro()
|
||||
{
|
||||
var state = mascotSkeletonGraphic.AnimationState;
|
||||
state.SetAnimation(0, jumpAnimation, false);
|
||||
@@ -59,19 +56,5 @@ namespace Darkmatter.Features.Mainmenu
|
||||
state.AddAnimation(0, idleAnimation, true, 0f);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPlayBtnClicked()
|
||||
{
|
||||
playBtn.interactable = false;
|
||||
playBtnAnimator.enabled = false;
|
||||
PlayBtnSequenceAsync(this.GetCancellationTokenOnDestroy()).Forget();
|
||||
}
|
||||
|
||||
private async UniTaskVoid PlayBtnSequenceAsync(CancellationToken token)
|
||||
{
|
||||
playBtnParticle.Play();
|
||||
await UniTask.WaitUntil(() => !playBtnParticle.IsAlive(true), cancellationToken: token);
|
||||
OnPlayBtnClickedEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Darkmatter.Features.Mainmenu
|
||||
{
|
||||
public class PlayButtonView : MonoBehaviour
|
||||
{
|
||||
private Button playBtn;
|
||||
private Animator playBtnAnimator;
|
||||
[SerializeField] private ParticleSystem playBtnParticle;
|
||||
|
||||
public event Action OnPlayBtnClickedEvent;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
playBtnAnimator = GetComponent<Animator>();
|
||||
playBtn = GetComponent<Button>();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
playBtn.onClick.AddListener(OnPlayBtnClicked);
|
||||
}
|
||||
|
||||
private void OnPlayBtnClicked()
|
||||
{
|
||||
playBtn.interactable = false;
|
||||
playBtnAnimator.enabled = false;
|
||||
PlayBtnSequenceAsync(this.GetCancellationTokenOnDestroy()).Forget();
|
||||
}
|
||||
|
||||
private async UniTaskVoid PlayBtnSequenceAsync(CancellationToken token)
|
||||
{
|
||||
playBtnParticle.Play();
|
||||
await UniTask.WaitUntil(() => !playBtnParticle.IsAlive(true), cancellationToken: token);
|
||||
OnPlayBtnClickedEvent?.Invoke();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 26f12f86a29f25046a03d5f29e123beb
|
||||
8
Assets/Darkmatter/Code/Features/MainMenuFlow.meta
Normal file
8
Assets/Darkmatter/Code/Features/MainMenuFlow.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b4bb322f193284fd9b4ebb75c3916408
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "Features.MainMenuFlow",
|
||||
"rootNamespace": "Darkmatter.Features.MainMenuFlow",
|
||||
"references": [
|
||||
"GUID:6a0a834eb41764f12ba55c3fb04a40cb",
|
||||
"GUID:b4c9f7fbf1e144933a1797dc208ece5f",
|
||||
"GUID:c1c03c0e5b2f4412b9f2be1c20d6a9b1",
|
||||
"GUID:729fabb77852b4d3eae2417d9564dc37",
|
||||
"GUID:b0214a6008ed146ff8f122a6a9c2f6cc",
|
||||
"GUID:f51ebe6a0ceec4240a699833d6309b23"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user