From a1d3788def18f42d9036dddee4364a0573f9612d Mon Sep 17 00:00:00 2001 From: Savya Bikram Shah Date: Wed, 27 May 2026 12:45:26 +0545 Subject: [PATCH 1/3] Undo system made --- .../Contracts/Features/History/ICommand.cs | 4 +- .../Features/History/Features.History.asmdef | 18 +++++++ .../History/Features.History.asmdef.meta | 7 +++ .../Code/Features/History/Installers.meta | 8 ++++ .../Installers/HistoryServiceModule.cs | 15 ++++++ .../Installers/HistoryServiceModule.cs.meta | 2 + .../Code/Features/History/Stack.meta | 8 ++++ .../Code/Features/History/Stack/UndoStack.cs | 47 +++++++++++++++++++ .../Features/History/Stack/UndoStack.cs.meta | 2 + Assets/Darkmatter/Scenes/GamePlay.unity | 46 ++++++++++++++++++ 10 files changed, 154 insertions(+), 3 deletions(-) create mode 100644 Assets/Darkmatter/Code/Features/History/Features.History.asmdef create mode 100644 Assets/Darkmatter/Code/Features/History/Features.History.asmdef.meta create mode 100644 Assets/Darkmatter/Code/Features/History/Installers.meta create mode 100644 Assets/Darkmatter/Code/Features/History/Installers/HistoryServiceModule.cs create mode 100644 Assets/Darkmatter/Code/Features/History/Installers/HistoryServiceModule.cs.meta create mode 100644 Assets/Darkmatter/Code/Features/History/Stack.meta create mode 100644 Assets/Darkmatter/Code/Features/History/Stack/UndoStack.cs create mode 100644 Assets/Darkmatter/Code/Features/History/Stack/UndoStack.cs.meta diff --git a/Assets/Darkmatter/Code/Core/Contracts/Features/History/ICommand.cs b/Assets/Darkmatter/Code/Core/Contracts/Features/History/ICommand.cs index a5da6f2..745de1f 100644 --- a/Assets/Darkmatter/Code/Core/Contracts/Features/History/ICommand.cs +++ b/Assets/Darkmatter/Code/Core/Contracts/Features/History/ICommand.cs @@ -1,6 +1,4 @@ -using UnityEngine; - -namespace Darkmatter.Core +namespace Darkmatter.Core.Contracts.Features.History { public interface ICommand { diff --git a/Assets/Darkmatter/Code/Features/History/Features.History.asmdef b/Assets/Darkmatter/Code/Features/History/Features.History.asmdef new file mode 100644 index 0000000..d5255d2 --- /dev/null +++ b/Assets/Darkmatter/Code/Features/History/Features.History.asmdef @@ -0,0 +1,18 @@ +{ + "name": "Features.History", + "rootNamespace": "Darkmatter.Features.History", + "references": [ + "GUID:6a0a834eb41764f12ba55c3fb04a40cb", + "GUID:b0214a6008ed146ff8f122a6a9c2f6cc", + "GUID:c1c03c0e5b2f4412b9f2be1c20d6a9b1" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Assets/Darkmatter/Code/Features/History/Features.History.asmdef.meta b/Assets/Darkmatter/Code/Features/History/Features.History.asmdef.meta new file mode 100644 index 0000000..821a536 --- /dev/null +++ b/Assets/Darkmatter/Code/Features/History/Features.History.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 3aa6224adf551496497bf0c866e704b5 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Darkmatter/Code/Features/History/Installers.meta b/Assets/Darkmatter/Code/Features/History/Installers.meta new file mode 100644 index 0000000..756ecc8 --- /dev/null +++ b/Assets/Darkmatter/Code/Features/History/Installers.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9cc86805d75dc4f3781d17d7c65ac1c8 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Darkmatter/Code/Features/History/Installers/HistoryServiceModule.cs b/Assets/Darkmatter/Code/Features/History/Installers/HistoryServiceModule.cs new file mode 100644 index 0000000..3daca18 --- /dev/null +++ b/Assets/Darkmatter/Code/Features/History/Installers/HistoryServiceModule.cs @@ -0,0 +1,15 @@ +using Darkmatter.Core.Contracts.Features.History; +using Darkmatter.Libs.Installers; +using UnityEngine; +using VContainer; + +namespace Darkmatter.Features.History +{ + public class HistoryServiceModule : MonoBehaviour,IServiceModule + { + public void Register(IContainerBuilder builder) + { + builder.Register(Lifetime.Singleton); + } + } +} diff --git a/Assets/Darkmatter/Code/Features/History/Installers/HistoryServiceModule.cs.meta b/Assets/Darkmatter/Code/Features/History/Installers/HistoryServiceModule.cs.meta new file mode 100644 index 0000000..f62df8c --- /dev/null +++ b/Assets/Darkmatter/Code/Features/History/Installers/HistoryServiceModule.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 45c42e41a28d34b01a364a3c2631ba73 \ No newline at end of file diff --git a/Assets/Darkmatter/Code/Features/History/Stack.meta b/Assets/Darkmatter/Code/Features/History/Stack.meta new file mode 100644 index 0000000..2b9cff6 --- /dev/null +++ b/Assets/Darkmatter/Code/Features/History/Stack.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 352fce035805a47c4aec94d870adea6d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Darkmatter/Code/Features/History/Stack/UndoStack.cs b/Assets/Darkmatter/Code/Features/History/Stack/UndoStack.cs new file mode 100644 index 0000000..a94eb23 --- /dev/null +++ b/Assets/Darkmatter/Code/Features/History/Stack/UndoStack.cs @@ -0,0 +1,47 @@ +using System.Collections.Generic; +using Darkmatter.Core.Contracts.Features.History; + +namespace Darkmatter.Features.History +{ + public sealed class UndoStack : IUndoStack + { + private const int Capacity = 20; + + private readonly LinkedList _undo = new(); + private readonly Stack _redo = new(); + + public bool CanUndo => _undo.Count > 0; + public bool CanRedo => _redo.Count > 0; + + public void Push(ICommand cmd) + { + cmd.Execute(); + _undo.AddLast(cmd); + if (_undo.Count > Capacity) _undo.RemoveFirst(); + _redo.Clear(); + } + + public void Undo() + { + if (!CanUndo) return; + var cmd = _undo.Last!.Value; + _undo.RemoveLast(); + cmd.Undo(); + _redo.Push(cmd); + } + + public void Redo() + { + if (!CanRedo) return; + var cmd = _redo.Pop(); + cmd.Execute(); + _undo.AddLast(cmd); + } + + public void Clear() + { + _undo.Clear(); + _redo.Clear(); + } + } +} \ No newline at end of file diff --git a/Assets/Darkmatter/Code/Features/History/Stack/UndoStack.cs.meta b/Assets/Darkmatter/Code/Features/History/Stack/UndoStack.cs.meta new file mode 100644 index 0000000..488bc88 --- /dev/null +++ b/Assets/Darkmatter/Code/Features/History/Stack/UndoStack.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 1a9ed946cf48e4bd582992ca4ca662a3 \ No newline at end of file diff --git a/Assets/Darkmatter/Scenes/GamePlay.unity b/Assets/Darkmatter/Scenes/GamePlay.unity index 9935902..9dc1b6f 100644 --- a/Assets/Darkmatter/Scenes/GamePlay.unity +++ b/Assets/Darkmatter/Scenes/GamePlay.unity @@ -461,6 +461,51 @@ MonoBehaviour: autoInjectGameObjects: [] serviceModules: - {fileID: 1594774441} + - {fileID: 1551649429} +--- !u!1 &1551649427 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1551649428} + - component: {fileID: 1551649429} + m_Layer: 0 + m_Name: HistoryServiceModule + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1551649428 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1551649427} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1965442263} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &1551649429 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1551649427} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 45c42e41a28d34b01a364a3c2631ba73, type: 3} + m_Name: + m_EditorClassIdentifier: Features.History::Darkmatter.Features.History.HistoryServiceModule --- !u!1 &1594774439 GameObject: m_ObjectHideFlags: 0 @@ -538,6 +583,7 @@ Transform: m_ConstrainProportionsScale: 0 m_Children: - {fileID: 1594774440} + - {fileID: 1551649428} m_Father: {fileID: 1224714932} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &2069155637 From f18b78db24d7fcbd014cc7c30080a02ce66ab204 Mon Sep 17 00:00:00 2001 From: Savya Bikram Shah Date: Wed, 27 May 2026 12:56:19 +0545 Subject: [PATCH 2/3] Removed Unnecessary stuff --- .../Code/Core/Contracts/Services/Inputs.meta | 8 - .../Contracts/Services/Inputs/IInputReader.cs | 12 - .../Services/Inputs/IInputReader.cs.meta | 2 - Assets/Darkmatter/Code/Features/PaperRig.meta | 8 - .../Code/Features/PaperRig/Core.meta | 8 - .../Code/Features/PaperRig/Core/PaperRig.cs | 15 -- .../Features/PaperRig/Core/PaperRig.cs.meta | 2 - .../PaperRig/Features.PaperRig.asmdef | 18 -- .../PaperRig/Features.PaperRig.asmdef.meta | 7 - .../Code/Features/PaperRig/Input.meta | 3 - .../Features/PaperRig/Input/ArtInputBridge.cs | 45 ---- .../PaperRig/Input/ArtInputBridge.cs.meta | 3 - .../Code/Features/PaperRig/Installers.meta | 8 - .../Installers/PaperRigServiceModule.cs | 20 -- .../Installers/PaperRigServiceModule.cs.meta | 2 - .../Code/Services/Inputs/Installers.meta | 8 - .../Inputs/Installers/InputServiceModule.cs | 19 -- .../Installers/InputServiceModule.cs.meta | 2 - .../Code/Services/Inputs/Readers.meta | 8 - .../Services/Inputs/Readers/InputReaderSO.cs | 47 ---- .../Inputs/Readers/InputReaderSO.cs.meta | 2 - Assets/Darkmatter/Scenes/Boot.unity | 49 +--- Assets/Darkmatter/Scenes/GamePlay.unity | 218 +----------------- 23 files changed, 9 insertions(+), 505 deletions(-) delete mode 100644 Assets/Darkmatter/Code/Core/Contracts/Services/Inputs.meta delete mode 100644 Assets/Darkmatter/Code/Core/Contracts/Services/Inputs/IInputReader.cs delete mode 100644 Assets/Darkmatter/Code/Core/Contracts/Services/Inputs/IInputReader.cs.meta delete mode 100644 Assets/Darkmatter/Code/Features/PaperRig.meta delete mode 100644 Assets/Darkmatter/Code/Features/PaperRig/Core.meta delete mode 100644 Assets/Darkmatter/Code/Features/PaperRig/Core/PaperRig.cs delete mode 100644 Assets/Darkmatter/Code/Features/PaperRig/Core/PaperRig.cs.meta delete mode 100644 Assets/Darkmatter/Code/Features/PaperRig/Features.PaperRig.asmdef delete mode 100644 Assets/Darkmatter/Code/Features/PaperRig/Features.PaperRig.asmdef.meta delete mode 100644 Assets/Darkmatter/Code/Features/PaperRig/Input.meta delete mode 100644 Assets/Darkmatter/Code/Features/PaperRig/Input/ArtInputBridge.cs delete mode 100644 Assets/Darkmatter/Code/Features/PaperRig/Input/ArtInputBridge.cs.meta delete mode 100644 Assets/Darkmatter/Code/Features/PaperRig/Installers.meta delete mode 100644 Assets/Darkmatter/Code/Features/PaperRig/Installers/PaperRigServiceModule.cs delete mode 100644 Assets/Darkmatter/Code/Features/PaperRig/Installers/PaperRigServiceModule.cs.meta delete mode 100644 Assets/Darkmatter/Code/Services/Inputs/Installers.meta delete mode 100644 Assets/Darkmatter/Code/Services/Inputs/Installers/InputServiceModule.cs delete mode 100644 Assets/Darkmatter/Code/Services/Inputs/Installers/InputServiceModule.cs.meta delete mode 100644 Assets/Darkmatter/Code/Services/Inputs/Readers.meta delete mode 100644 Assets/Darkmatter/Code/Services/Inputs/Readers/InputReaderSO.cs delete mode 100644 Assets/Darkmatter/Code/Services/Inputs/Readers/InputReaderSO.cs.meta diff --git a/Assets/Darkmatter/Code/Core/Contracts/Services/Inputs.meta b/Assets/Darkmatter/Code/Core/Contracts/Services/Inputs.meta deleted file mode 100644 index 52b9ea7..0000000 --- a/Assets/Darkmatter/Code/Core/Contracts/Services/Inputs.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: f8c5a5b38d3aa43adae4dd7df1b8184c -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Darkmatter/Code/Core/Contracts/Services/Inputs/IInputReader.cs b/Assets/Darkmatter/Code/Core/Contracts/Services/Inputs/IInputReader.cs deleted file mode 100644 index 3deb107..0000000 --- a/Assets/Darkmatter/Code/Core/Contracts/Services/Inputs/IInputReader.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using UnityEngine; - -namespace Darkmatter.Core.Contracts.Services.Inputs -{ - public interface IInputReader - { - Vector2 TouchPosition { get; } - event Action OnTouchStart; - event Action OnTouchEnd; - } -} \ No newline at end of file diff --git a/Assets/Darkmatter/Code/Core/Contracts/Services/Inputs/IInputReader.cs.meta b/Assets/Darkmatter/Code/Core/Contracts/Services/Inputs/IInputReader.cs.meta deleted file mode 100644 index 6fd0e4b..0000000 --- a/Assets/Darkmatter/Code/Core/Contracts/Services/Inputs/IInputReader.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 882d89c37a9f543c09ebe4b15395da7d \ No newline at end of file diff --git a/Assets/Darkmatter/Code/Features/PaperRig.meta b/Assets/Darkmatter/Code/Features/PaperRig.meta deleted file mode 100644 index 04bd348..0000000 --- a/Assets/Darkmatter/Code/Features/PaperRig.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: f555e5ed9c45e44cd804a598d08efc6c -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Darkmatter/Code/Features/PaperRig/Core.meta b/Assets/Darkmatter/Code/Features/PaperRig/Core.meta deleted file mode 100644 index f89af9a..0000000 --- a/Assets/Darkmatter/Code/Features/PaperRig/Core.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: f26c384b6ed9f482b8b3f325ba5e06f5 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Darkmatter/Code/Features/PaperRig/Core/PaperRig.cs b/Assets/Darkmatter/Code/Features/PaperRig/Core/PaperRig.cs deleted file mode 100644 index e5d2315..0000000 --- a/Assets/Darkmatter/Code/Features/PaperRig/Core/PaperRig.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using Darkmatter.Core.Contracts.Features.Paper; -using UnityEngine; - -namespace Darkmatter.Features.PaperRig -{ - [Serializable] - public class PaperRig : IPaperRig - { - [SerializeField] private Camera artCamera; - [SerializeField] private RectTransform displayRect; - public Camera ArtCamera => artCamera; - public RectTransform DisplayRect => displayRect; - } -} \ No newline at end of file diff --git a/Assets/Darkmatter/Code/Features/PaperRig/Core/PaperRig.cs.meta b/Assets/Darkmatter/Code/Features/PaperRig/Core/PaperRig.cs.meta deleted file mode 100644 index e43a7cd..0000000 --- a/Assets/Darkmatter/Code/Features/PaperRig/Core/PaperRig.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: fd7db17694325451c8441d824b221199 \ No newline at end of file diff --git a/Assets/Darkmatter/Code/Features/PaperRig/Features.PaperRig.asmdef b/Assets/Darkmatter/Code/Features/PaperRig/Features.PaperRig.asmdef deleted file mode 100644 index 76f7125..0000000 --- a/Assets/Darkmatter/Code/Features/PaperRig/Features.PaperRig.asmdef +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "Features.PaperRig", - "rootNamespace": "Darkmatter.Features.PaperRig", - "references": [ - "GUID:6a0a834eb41764f12ba55c3fb04a40cb", - "GUID:b0214a6008ed146ff8f122a6a9c2f6cc", - "GUID:c1c03c0e5b2f4412b9f2be1c20d6a9b1" - ], - "includePlatforms": [], - "excludePlatforms": [], - "allowUnsafeCode": false, - "overrideReferences": false, - "precompiledReferences": [], - "autoReferenced": true, - "defineConstraints": [], - "versionDefines": [], - "noEngineReferences": false -} \ No newline at end of file diff --git a/Assets/Darkmatter/Code/Features/PaperRig/Features.PaperRig.asmdef.meta b/Assets/Darkmatter/Code/Features/PaperRig/Features.PaperRig.asmdef.meta deleted file mode 100644 index c92a7f7..0000000 --- a/Assets/Darkmatter/Code/Features/PaperRig/Features.PaperRig.asmdef.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: c54b3e6267ecb41b68cc06079d4b39f2 -AssemblyDefinitionImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Darkmatter/Code/Features/PaperRig/Input.meta b/Assets/Darkmatter/Code/Features/PaperRig/Input.meta deleted file mode 100644 index ca62caf..0000000 --- a/Assets/Darkmatter/Code/Features/PaperRig/Input.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 0156c96d959344bfbf0c1212ea68d473 -timeCreated: 1779859615 \ No newline at end of file diff --git a/Assets/Darkmatter/Code/Features/PaperRig/Input/ArtInputBridge.cs b/Assets/Darkmatter/Code/Features/PaperRig/Input/ArtInputBridge.cs deleted file mode 100644 index 7d6a286..0000000 --- a/Assets/Darkmatter/Code/Features/PaperRig/Input/ArtInputBridge.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Darkmatter.Core.Contracts.Features.Paper; -using Darkmatter.Core.Contracts.Services.Camera; -using UnityEngine; -using CameraType = Darkmatter.Core.Enums.Services.Camera.CameraType; - -namespace Darkmatter.Features.PaperRig.Darkmatter.Code.Features.PaperRig.Input -{ - public class ArtInputBridge : IArtInputBridge - { - private readonly IPaperRig _paperRig; - private ICameraService _cameraService; - - public ArtInputBridge(IPaperRig paperRig, ICameraService cameraService) - { - _paperRig = paperRig; - _cameraService = cameraService; - } - - public bool TryScreenToArtWorld(Vector2 screenPos, out Vector2 artWorldPos) - { - var rectT = _paperRig.DisplayRect; - var uiCamera = _cameraService.GetCamera(CameraType.UICamera); - if (!RectTransformUtility.ScreenPointToLocalPointInRectangle( - rectT, screenPos, uiCamera, out var local)) - { - artWorldPos = default; - return false; - } - - var rect = rectT.rect; - var uv = new Vector2( - (local.x - rect.xMin) / rect.width, - (local.y - rect.yMin) / rect.height); - - if (uv.x < 0 || uv.x > 1 || uv.y < 0 || uv.y > 1) - { - artWorldPos = default; - return false; - } - - artWorldPos = _paperRig.ArtCamera.ViewportToWorldPoint(uv); - return true; - } - } -} \ No newline at end of file diff --git a/Assets/Darkmatter/Code/Features/PaperRig/Input/ArtInputBridge.cs.meta b/Assets/Darkmatter/Code/Features/PaperRig/Input/ArtInputBridge.cs.meta deleted file mode 100644 index 530d66c..0000000 --- a/Assets/Darkmatter/Code/Features/PaperRig/Input/ArtInputBridge.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 0337c48e57934443887873de8e382d4e -timeCreated: 1779859625 \ No newline at end of file diff --git a/Assets/Darkmatter/Code/Features/PaperRig/Installers.meta b/Assets/Darkmatter/Code/Features/PaperRig/Installers.meta deleted file mode 100644 index c81516f..0000000 --- a/Assets/Darkmatter/Code/Features/PaperRig/Installers.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: ecba85ae080a44ef9b1a1d55f3f86659 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Darkmatter/Code/Features/PaperRig/Installers/PaperRigServiceModule.cs b/Assets/Darkmatter/Code/Features/PaperRig/Installers/PaperRigServiceModule.cs deleted file mode 100644 index be9398c..0000000 --- a/Assets/Darkmatter/Code/Features/PaperRig/Installers/PaperRigServiceModule.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Darkmatter.Core.Contracts.Features.Paper; -using Darkmatter.Features.PaperRig.Darkmatter.Code.Features.PaperRig.Input; -using Darkmatter.Libs.Installers; -using UnityEngine; -using VContainer; -using VContainer.Unity; - -namespace Darkmatter.Features.PaperRig -{ - public class PaperRigServiceModule : MonoBehaviour, IServiceModule - { - [SerializeField] private PaperRig paperRig; - - public void Register(IContainerBuilder builder) - { - builder.RegisterComponent(paperRig); - builder.Register(Lifetime.Singleton); - } - } -} \ No newline at end of file diff --git a/Assets/Darkmatter/Code/Features/PaperRig/Installers/PaperRigServiceModule.cs.meta b/Assets/Darkmatter/Code/Features/PaperRig/Installers/PaperRigServiceModule.cs.meta deleted file mode 100644 index 32b809a..0000000 --- a/Assets/Darkmatter/Code/Features/PaperRig/Installers/PaperRigServiceModule.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: ef7eaa67fc66d48c88c8ec12d27f9b14 \ No newline at end of file diff --git a/Assets/Darkmatter/Code/Services/Inputs/Installers.meta b/Assets/Darkmatter/Code/Services/Inputs/Installers.meta deleted file mode 100644 index 3c79780..0000000 --- a/Assets/Darkmatter/Code/Services/Inputs/Installers.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 588d1c926497b491c96d2f405876b176 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Darkmatter/Code/Services/Inputs/Installers/InputServiceModule.cs b/Assets/Darkmatter/Code/Services/Inputs/Installers/InputServiceModule.cs deleted file mode 100644 index 1fdd172..0000000 --- a/Assets/Darkmatter/Code/Services/Inputs/Installers/InputServiceModule.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Darkmatter.Core.Contracts.Services.Inputs; -using Darkmatter.Libs.Installers; -using Darkmatter.Services.Inputs.Readers; -using UnityEngine; -using VContainer; -using VContainer.Unity; - -namespace Darkmatter.Services.Inputs -{ - public class InputServiceModule : MonoBehaviour, IServiceModule - { - [SerializeField] private InputReaderSO inputReaderSO; - - public void Register(IContainerBuilder builder) - { - builder.RegisterComponent(inputReaderSO); - } - } -} \ No newline at end of file diff --git a/Assets/Darkmatter/Code/Services/Inputs/Installers/InputServiceModule.cs.meta b/Assets/Darkmatter/Code/Services/Inputs/Installers/InputServiceModule.cs.meta deleted file mode 100644 index 15ac890..0000000 --- a/Assets/Darkmatter/Code/Services/Inputs/Installers/InputServiceModule.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 1b23ca8ea5ee647ddba0712811953811 \ No newline at end of file diff --git a/Assets/Darkmatter/Code/Services/Inputs/Readers.meta b/Assets/Darkmatter/Code/Services/Inputs/Readers.meta deleted file mode 100644 index 4fde7d9..0000000 --- a/Assets/Darkmatter/Code/Services/Inputs/Readers.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 2e8e28c4942b5410d962cbff40ac302f -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Darkmatter/Code/Services/Inputs/Readers/InputReaderSO.cs b/Assets/Darkmatter/Code/Services/Inputs/Readers/InputReaderSO.cs deleted file mode 100644 index bfb5612..0000000 --- a/Assets/Darkmatter/Code/Services/Inputs/Readers/InputReaderSO.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using Darkmatter.Core.Contracts.Services.Inputs; -using UnityEngine; -using UnityEngine.InputSystem; - -namespace Darkmatter.Services.Inputs.Readers -{ - [CreateAssetMenu(menuName = "Darkmatter/Inputs/New Input Reader")] - public class InputReaderSO : ScriptableObject, IInputReader, GameInputs.IPlayerActions - { - public Vector2 TouchPosition { get; private set; } - public event Action OnTouchStart; - public event Action OnTouchEnd; - - private GameInputs _gameInputActions; - - private void OnEnable() - { - _gameInputActions = new GameInputs(); - _gameInputActions.Player.SetCallbacks(this); - _gameInputActions.Enable(); - } - - public void OnTouchPosition(InputAction.CallbackContext context) - { - TouchPosition = context.ReadValue(); - } - - public void OnTouched(InputAction.CallbackContext context) - { - if(context.started) - OnTouchStart?.Invoke(); - else if(context.canceled) - OnTouchEnd?.Invoke(); - } - - private void OnDisable() - { - if (_gameInputActions != null) - { - _gameInputActions.Disable(); - _gameInputActions.Dispose(); - _gameInputActions = null; - } - } - } -} \ No newline at end of file diff --git a/Assets/Darkmatter/Code/Services/Inputs/Readers/InputReaderSO.cs.meta b/Assets/Darkmatter/Code/Services/Inputs/Readers/InputReaderSO.cs.meta deleted file mode 100644 index fcb8de0..0000000 --- a/Assets/Darkmatter/Code/Services/Inputs/Readers/InputReaderSO.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: c7a7f36a3426c43b7a1ceeaa853bdc3e \ No newline at end of file diff --git a/Assets/Darkmatter/Scenes/Boot.unity b/Assets/Darkmatter/Scenes/Boot.unity index 8b28dd5..24bc884 100644 --- a/Assets/Darkmatter/Scenes/Boot.unity +++ b/Assets/Darkmatter/Scenes/Boot.unity @@ -247,51 +247,6 @@ Transform: m_Children: [] m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!1 &147402014 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 147402015} - - component: {fileID: 147402016} - m_Layer: 0 - m_Name: InputServiceModules - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!4 &147402015 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 147402014} - serializedVersion: 2 - m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} - m_LocalPosition: {x: 16.30939, y: 8.32207, z: 0} - m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 0 - m_Children: [] - m_Father: {fileID: 274737044} - m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!114 &147402016 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 147402014} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 1b23ca8ea5ee647ddba0712811953811, type: 3} - m_Name: - m_EditorClassIdentifier: Services.Inputs::Darkmatter.Services.Inputs.InputServiceModule - inputReaderSO: {fileID: 11400000, guid: f9b7ed848ae3b4036923bbbb2f77fe40, type: 2} --- !u!1 &274737043 GameObject: m_ObjectHideFlags: 0 @@ -322,7 +277,6 @@ Transform: m_ConstrainProportionsScale: 0 m_Children: - {fileID: 292698384} - - {fileID: 147402015} m_Father: {fileID: 329578012} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &292698383 @@ -417,12 +371,11 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: Darkmatter.App::GameLifetimeScope parentReference: - TypeName: RootLifetimeScope + TypeName: autoRun: 1 autoInjectGameObjects: [] serviceModules: - {fileID: 292698385} - - {fileID: 147402016} --- !u!1 &519420028 GameObject: m_ObjectHideFlags: 0 diff --git a/Assets/Darkmatter/Scenes/GamePlay.unity b/Assets/Darkmatter/Scenes/GamePlay.unity index 9dc1b6f..c81b655 100644 --- a/Assets/Darkmatter/Scenes/GamePlay.unity +++ b/Assets/Darkmatter/Scenes/GamePlay.unity @@ -282,134 +282,6 @@ Transform: m_Children: [] m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!1 &942391588 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 942391592} - - component: {fileID: 942391591} - - component: {fileID: 942391589} - m_Layer: 0 - m_Name: ArtCamera - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!114 &942391589 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 942391588} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: a79441f348de89743a2939f4d699eac1, type: 3} - m_Name: - m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Runtime::UnityEngine.Rendering.Universal.UniversalAdditionalCameraData - m_RenderShadows: 1 - m_RequiresDepthTextureOption: 2 - m_RequiresOpaqueTextureOption: 2 - m_CameraType: 0 - m_Cameras: [] - m_RendererIndex: -1 - m_VolumeLayerMask: - serializedVersion: 2 - m_Bits: 1 - m_VolumeTrigger: {fileID: 0} - m_VolumeFrameworkUpdateModeOption: 2 - m_RenderPostProcessing: 0 - m_Antialiasing: 0 - m_AntialiasingQuality: 2 - m_StopNaN: 0 - m_Dithering: 0 - m_ClearDepth: 1 - m_AllowXRRendering: 1 - m_AllowHDROutput: 1 - m_UseScreenCoordOverride: 0 - m_ScreenSizeOverride: {x: 0, y: 0, z: 0, w: 0} - m_ScreenCoordScaleBias: {x: 0, y: 0, z: 0, w: 0} - m_RequiresDepthTexture: 0 - m_RequiresColorTexture: 0 - m_TaaSettings: - m_Quality: 3 - m_FrameInfluence: 0.1 - m_JitterScale: 1 - m_MipBias: 0 - m_VarianceClampScale: 0.9 - m_ContrastAdaptiveSharpening: 0 - m_Version: 2 ---- !u!20 &942391591 -Camera: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 942391588} - m_Enabled: 1 - serializedVersion: 2 - m_ClearFlags: 1 - m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} - m_projectionMatrixMode: 1 - m_GateFitMode: 2 - m_FOVAxisMode: 0 - m_Iso: 200 - m_ShutterSpeed: 0.005 - m_Aperture: 16 - m_FocusDistance: 10 - m_FocalLength: 50 - m_BladeCount: 5 - m_Curvature: {x: 2, y: 11} - m_BarrelClipping: 0.25 - m_Anamorphism: 0 - m_SensorSize: {x: 36, y: 24} - m_LensShift: {x: 0, y: 0} - m_NormalizedViewPortRect: - serializedVersion: 2 - x: 0 - y: 0 - width: 1 - height: 1 - near clip plane: 0.3 - far clip plane: 1000 - field of view: 60 - orthographic: 1 - orthographic size: 5 - m_Depth: 0 - m_CullingMask: - serializedVersion: 2 - m_Bits: 23 - m_RenderingPath: -1 - m_TargetTexture: {fileID: 8400000, guid: 00ba58b6f31d046c1849805f2cb9a57d, type: 2} - m_TargetDisplay: 0 - m_TargetEye: 3 - m_HDR: 1 - m_AllowMSAA: 1 - m_AllowDynamicResolution: 0 - m_ForceIntoRT: 0 - m_OcclusionCulling: 1 - m_StereoConvergence: 10 - m_StereoSeparation: 0.022 ---- !u!4 &942391592 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 942391588} - serializedVersion: 2 - m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0, y: 0, z: -10} - m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 0 - m_Children: [] - m_Father: {fileID: 0} - m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &1224714931 GameObject: m_ObjectHideFlags: 0 @@ -551,8 +423,8 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: Features.PaperRig::Darkmatter.Features.PaperRig.PaperRigServiceModule paperRig: - artCamera: {fileID: 942391591} - displayRect: {fileID: 2081960987} + artCamera: {fileID: 0} + displayRect: {fileID: 0} --- !u!1 &1965442262 GameObject: m_ObjectHideFlags: 0 @@ -634,12 +506,12 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3} m_Name: m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.CanvasScaler - m_UiScaleMode: 0 + m_UiScaleMode: 1 m_ReferencePixelsPerUnit: 100 m_ScaleFactor: 1 - m_ReferenceResolution: {x: 800, y: 600} + m_ReferenceResolution: {x: 1920, y: 1080} m_ScreenMatchMode: 0 - m_MatchWidthOrHeight: 0 + m_MatchWidthOrHeight: 0.5 m_PhysicalUnit: 3 m_FallbackScreenDPI: 96 m_DefaultSpriteDPI: 96 @@ -654,7 +526,7 @@ Canvas: m_GameObject: {fileID: 2069155637} m_Enabled: 1 serializedVersion: 3 - m_RenderMode: 0 + m_RenderMode: 1 m_Camera: {fileID: 0} m_PlaneDistance: 100 m_PixelPerfect: 0 @@ -662,7 +534,7 @@ Canvas: m_OverrideSorting: 0 m_OverridePixelPerfect: 0 m_SortingBucketNormalizedSize: 0 - m_VertexColorAlwaysGammaSpace: 0 + m_VertexColorAlwaysGammaSpace: 1 m_UseReflectionProbes: 0 m_AdditionalShaderChannelsFlag: 0 m_UpdateRectTransformForStandalone: 0 @@ -680,8 +552,7 @@ RectTransform: m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 0, y: 0, z: 0} m_ConstrainProportionsScale: 0 - m_Children: - - {fileID: 2081960987} + m_Children: [] m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} @@ -689,84 +560,11 @@ RectTransform: m_AnchoredPosition: {x: 0, y: 0} m_SizeDelta: {x: 0, y: 0} m_Pivot: {x: 0, y: 0} ---- !u!1 &2081960986 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 2081960987} - - component: {fileID: 2081960989} - - component: {fileID: 2081960988} - m_Layer: 5 - m_Name: ArtCanvas - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!224 &2081960987 -RectTransform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 2081960986} - m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0, y: 0, z: 0} - m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 0 - m_Children: [] - m_Father: {fileID: 2069155641} - m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} - m_AnchorMin: {x: 0, y: 0} - m_AnchorMax: {x: 1, y: 1} - m_AnchoredPosition: {x: 0, y: 0} - m_SizeDelta: {x: 0, y: 0} - m_Pivot: {x: 0.5, y: 0.5} ---- !u!114 &2081960988 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 2081960986} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 1344c3c82d62a2a41a3576d8abb8e3ea, type: 3} - m_Name: - m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.RawImage - m_Material: {fileID: 0} - m_Color: {r: 1, g: 1, b: 1, a: 1} - m_RaycastTarget: 1 - m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} - m_Maskable: 1 - m_OnCullStateChanged: - m_PersistentCalls: - m_Calls: [] - m_Texture: {fileID: 8400000, guid: 00ba58b6f31d046c1849805f2cb9a57d, type: 2} - m_UVRect: - serializedVersion: 2 - x: 0 - y: 0 - width: 1 - height: 1 ---- !u!222 &2081960989 -CanvasRenderer: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 2081960986} - m_CullTransparentMesh: 1 --- !u!1660057539 &9223372036854775807 SceneRoots: m_ObjectHideFlags: 0 m_Roots: - {fileID: 619394802} - - {fileID: 942391592} - {fileID: 2069155641} - {fileID: 590523275} - {fileID: 1224714932} From d3d16439b6a1350f4622e2acc5109d2dae798b07 Mon Sep 17 00:00:00 2001 From: Savya Bikram Shah Date: Wed, 27 May 2026 13:13:47 +0545 Subject: [PATCH 3/3] Readme asset --- Readme.md | 491 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 273 insertions(+), 218 deletions(-) diff --git a/Readme.md b/Readme.md index 8198134..8eb88c6 100644 --- a/Readme.md +++ b/Readme.md @@ -98,7 +98,7 @@ Assets/Darkmatter/ │ ├── Compatibility/ │ │ └── IsExternalInit.cs (C#9 init shim for older runtimes) │ ├── Contracts/ - │ │ ├── Paper/ ← misplaced empty folder — should be Contracts/Features/Paper/ (delete or move) + │ │ ├── Paper/ ← misplaced empty folder — move to Contracts/Features/Paper/ when IPaperSurface lands │ │ └── Services/ │ │ ├── Assets/IAssetProviderService.cs │ │ ├── Audio/IAudioService.cs, ISfxPlayer.cs @@ -175,7 +175,7 @@ Rough landing order for ColorBook scene to be playable: | Path | Role | |---|---| -| `Core/Contracts/Features/Paper/IPaperRig.cs`, `IArtInputBridge.cs` | Paper rig contracts | +| `Core/Contracts/Features/Paper/IPaperSurface.cs` | Paper surface contract (canvas roots) | | `Core/Contracts/Services/Capture/ICaptureService.cs` | Capture service contract | | `Core/Contracts/Services/Gallery/IGalleryService.cs` | Gallery service contract | | `Core/Contracts/Features/Drawing/IDrawingTemplate.cs`, `IDrawingTemplateCatalog.cs` | Drawing template contracts | @@ -190,9 +190,9 @@ Rough landing order for ColorBook scene to be playable: | `Core/Data/Dynamic/Features/Signals/` (DrawingSelectedSignal, ShapeAssembledSignal, ColorAppliedSignal, ArtworkCapturedSignal, ArtworkSavedSignal) | Cross-feature signal structs | | `Core/Enums/Services/Camera/CameraType.cs` | Add `ArtCamera` enum value to existing file | | `Libs/CommandStack/` (+ `Libs.CommandStack.asmdef`) | Bounded undo/redo | -| `Services/Capture/` (+ `Services.Capture.asmdef`) | `RenderTextureCaptureService` reads `IPaperRig.Surface` | +| `Services/Capture/` (+ `Services.Capture.asmdef`) | `RenderTextureCaptureService` drives the disabled `CaptureCamera` | | `Services/Gallery/` (+ `Services.Gallery.asmdef`) | `FileGalleryService` — PNG + sidecar JSON IO | -| `Features/Paper/` (+ `Features.Paper.asmdef`) | Scene-bound RT rig | +| `Features/Paper/` (+ `Features.Paper.asmdef`) | Scene-bound `PaperSurface` MB + module | | `Features/{MainMenu,DrawingCatalog,ShapeBuilder,Coloring,History,Capture,Progression,ColorBookFlow,ArtBook}/` (+ asmdefs each) | Game features | | `App/LifetimeScopes/{MainMenu,ColorBook,ArtBook}LifetimeScope.cs` | Per-scene scopes | | `App/Boot/AppBoot.cs` | Bootstrap entry point | @@ -283,59 +283,87 @@ Failures show a child-friendly retry screen; never crash. ## 7. Rendering Strategy -**RT-as-paper.** ArtCamera renders the drawing world to an offscreen `RenderTexture`. A Canvas `RawImage` displays that RT. HUD lives on the same Canvas, above the RawImage. The RT *is* the paper — same fixed coordinate system on every device. +**Full Canvas UI.** No `SpriteRenderer`, no `Physics2D`, no offscreen `RenderTexture` for the live view. The paper, slots, pieces, and color regions are all `Image` components on a Screen-Space-Camera canvas. Standard Unity UI eventing (`IPointerDownHandler`, `IDragHandler`) handles all input. ``` -┌──────────────────────────────────────────────────────┐ -│ UICanvas (Screen-Space - Camera, UICamera) │ -│ │ -│ ┌────────────────────────────────────┐ │ -│ │ RawImage (AspectRatioFitter 1:1) │ [HUD] │ -│ │ └─ texture = PaperRig.Surface │ palette │ -│ │ │ undo etc │ -│ │ ArtCamera renders → here │ │ -│ └────────────────────────────────────┘ │ -│ │ -└──────────────────────────────────────────────────────┘ - ▲ - │ rendered offscreen - │ - ArtCamera (orthographicSize fixed, aspect = 1f) - culling mask: Artwork, PaperBackground, Effects - target texture: PaperRig.Surface (2048×2048 ARGB32) +┌──────────────────────────────────────────────────────────┐ +│ PaperCanvas (Screen Space - Camera, UICamera) │ +│ layer: PaperUI │ +│ │ +│ ┌──────────────────────────────────────────────────┐ │ +│ │ PaperPanel (RectTransform, 2048×2048 ref units) │ │ +│ │ ├─ BackgroundImage │ │ +│ │ ├─ SlotsPanel (slot Image outlines) │ │ +│ │ ├─ PiecesPanel (draggable piece Images) │ │ +│ │ └─ RegionsPanel (colorable region Images) │ │ +│ └──────────────────────────────────────────────────┘ │ +│ │ +└──────────────────────────────────────────────────────────┘ + +┌──────────────────────────────────────────────────────────┐ +│ HUDCanvas (Screen Space - Overlay, OR separate camera) │ +│ layer: HUDUI │ +│ ├─ Palette panel │ +│ ├─ Undo / Redo buttons │ +│ ├─ Capture / Next buttons │ +│ └─ Tray panel (during build phase) │ +└──────────────────────────────────────────────────────────┘ + +CaptureCamera (disabled by default, one-shot Render() on capture) + orthographic, projection cloned from UICamera + cullingMask = PaperUI only + targetTexture = temp RT allocated per capture (2048×2048) ``` ### Cameras -| Camera | Type | Culling Mask | Render Target | Purpose | +| Camera | Render mode | Culling Mask | Render Target | Purpose | |---|---|---|---|---| -| `ArtCamera` | Orthographic, **fixed ortho size**, aspect = 1 | `Artwork`, `PaperBackground`, `Effects` | `PaperRig.Surface` (offscreen RT) | Renders the drawing world. Never sees the screen. | -| `UICamera` | Camera (Screen-Space – Camera) | `UI` | Screen | Displays the paper RawImage + HUD. | +| `UICamera` | Screen-Space - Camera (orthographic) | `PaperUI`, `HUDUI` | Screen | Normal display each frame. | +| `CaptureCamera` | Orthographic, disabled | `PaperUI` only | temp `RenderTexture` | One-shot `Render()` invoked by `ICaptureService.CaptureAsync()`. | + +`CaptureCamera` shares `UICamera`'s position, orthographic size, and clip planes so the captured frame matches what the player sees — minus the HUD because of the culling mask. ### Layers | Layer | Used by | |---|---| -| `Artwork` | Drawing region sprites, shape pieces, paper bg, all in ArtCamera world | -| `Effects` | Particle bursts, sparkles — also in ArtCamera world (so they're captured into the PNG) | -| `UI` | All Canvas elements (RawImage paper + HUD) | +| `PaperUI` | `PaperCanvas` and all of its children (background, slots, pieces, regions, completion FX). Visible in capture. | +| `HUDUI` | `HUDCanvas` and tray panel (palette, undo/redo, capture button, drawing catalog grid, etc.). Excluded from capture. | +| `EventSystem` | Unity's input layer — managed automatically. | -### Why RT-as-paper +### Why full UI | Need | Choice | Why | |---|---|---| -| Per-region tap-to-fill | Sprites + `PolygonCollider2D` in ArtCamera world; tapped via `IArtInputBridge` | Coordinate system is fixed (RT space). One `Physics2D.OverlapPoint` call after screen→art-world conversion. | -| Drag/drop shape pieces | Sprites + Physics2D in art world | Same fixed bounds on every device — no per-aspect tray layout. | -| Capture to PNG | `RT → Texture2D → PNG` | The RT *is* the saved image. No camera state override, no compositing pass, no determinism worries. | -| Multi-resolution support | `AspectRatioFitter (1:1, FitInParent)` on the RawImage | The "fit camera" problem reduces to a single Canvas property. Letterbox/pillarbox = whatever the Canvas around the RawImage looks like. | -| Color palette, buttons | Canvas above the RawImage | Anchors handle aspect ratios. Buttons + ScrollRect free. | -| Drawing catalog grid | Canvas | `GridLayoutGroup` + ScrollRect, async thumbnail loader. | +| Tap-to-paint region | `Image` + `Image.alphaHitTestMinimumThreshold` + `IPointerClickHandler` | Tight alpha-based hit shape per region. No mesh / collider authoring. Tap events route through `EventSystem` natively. | +| Drag/drop shape pieces | `Image` + `IBeginDragHandler` / `IDragHandler` / `IEndDragHandler` | Standard Unity UI drag. Pointer events come in canvas-local coords already. No screen→world math anywhere. | +| Visual transition during drag → snap | `DOAnchorPos`, `DOSizeDelta`, `DOLocalRotate` | All pose is in `RectTransform` units. The "transition" is a tween over canvas-local values — no swap of render context. | +| Capture to PNG | Dedicated `CaptureCamera` with `cullingMask = PaperUI` | One `Render()` call into a temp RT. HUD physically can't appear. | +| Multi-resolution support | `CanvasScaler` on `PaperCanvas` (Scale With Screen Size) | Reference resolution `2048 × 2048`, Match = 1 (height). All `anchoredPosition` units are constant across devices. | +| HUD layout independent of paper | `HUDCanvas` (separate Canvas) | HUD scales/anchors per its own rules without affecting the paper layout. | +| Drawing catalog grid, palette, etc. | Standard UI (`GridLayoutGroup`, `ScrollRect`, `Button`) | Anchors handle aspect ratios. Async thumbnail loader. | ### Multi-resolution rule -The artwork world is **screen-size-independent by construction.** Author every drawing in a fixed 2048×2048 design rect (or 20×20 world units at PPU=100). Pieces, regions, snap radii, slot positions — all expressed in this space and never scaled at runtime. Different screen sizes only change how the *RawImage* is laid out on the Canvas; the contents of the RT stay identical. +The paper content is **canvas-unit-stable.** Author every drawing against a fixed 2048 × 2048 reference resolution. Slot positions, piece sizes, region rects, hit shapes — all expressed in `anchoredPosition` / `sizeDelta` units. `CanvasScaler` on `PaperCanvas` does the screen mapping. -If you need a backdrop (wood/cloth behind the paper), it's a sibling Canvas Image *outside* the RawImage, sized to fill the screen. The RT itself has a transparent or paper-colored background. +`PaperPanel` is anchored center, fixed 2048×2048 (or whatever you pick for the reference). On a wider screen, `CanvasScaler` pillarboxes the panel; on a narrower screen, it letterboxes. The panel's contents never resize relative to each other. + +If you want a backdrop (wood/cloth behind the paper area), it's a sibling `Image` of `PaperPanel` (still on `PaperUI` layer) sized to fill the canvas. The backdrop *is* captured into the PNG by default — set its layer to `HUDUI` if you want it excluded. + +### Tradeoff vs the old RT-paper-rig design + +| Concern | RT-paper-rig (old) | Canvas-only (current) | +|---|---|---| +| Files in `IPaperRig` / `IArtInputBridge` | 2 contracts + ~80 lines of math | gone | +| Input pipeline | `IInputReader` → bridge → `Physics2D.OverlapPoint` | native `EventSystem` (`IPointerDownHandler` etc.) | +| Coloring hit shape | `PolygonCollider2D` from `Sprite.Editor` physics shape | `Image.alphaHitTestMinimumThreshold = 0.5f` on the region sprite | +| Per-frame render passes | 2 (ArtCamera into RT + UICamera draws RawImage) | 1 (UICamera draws everything) | +| Capture | read persistent RT | one-shot `CaptureCamera.Render()` | +| Coordinate gotchas | mismatches between screen / RT / world | none — everything is canvas-local | + +If you ever need world-space effects (particle sparkles that physically explode outside the paper, free-draw brush stroke, pinch zoom on the artwork), revisit the RT approach. For the v1 tap-to-fill + drag-to-snap design, Canvas-only is correct. --- @@ -361,17 +389,22 @@ public interface IDrawingTemplate { public readonly struct ShapePieceDTO { public string PieceId { get; } - public Sprite Sprite { get; } - public Vector2 SlotPosition { get; } - public float SlotRotation { get; } - public float SnapRadius { get; } // generous for toddlers + public Sprite Sprite { get; } // assigned to Image.sprite + public Vector2 SlotAnchoredPosition { get; } // canvas units, relative to SlotsParent + public Vector2 SlotSizeDelta { get; } // canvas units — target size when snapped + public float SlotRotationZ { get; } // degrees, local rotation when snapped + public float SnapRadius { get; } // canvas units; ~80–120 for toddlers + public float PreviewRadius { get; } // canvas units; ~2× snap radius } public readonly struct ColorRegionDTO { public string RegionId { get; } - public Sprite Sprite { get; } // sprite renderer source - public Vector2[] ColliderPath { get; } // polygon collider points - public Color InitialColor { get; } // usually white + public Sprite Sprite { get; } // assigned to Image.sprite + public Vector2 AnchoredPosition { get; } // canvas units, relative to RegionsParent + public Vector2 SizeDelta { get; } // canvas units + public Color InitialColor { get; } // usually white + // Hit shape comes from the sprite alpha — set Image.alphaHitTestMinimumThreshold = 0.5. + // No polygon path needed; sprite import settings ("Read/Write Enabled") provide it. } ``` @@ -394,31 +427,25 @@ public readonly struct PaintCommandDTO { } ``` -### Paper (RT rig + input bridge) +### Paper (canvas surface root) > Contracts live in `Darkmatter.Core.Contracts.Features.Paper`. Files at `Core/Contracts/Features/Paper/`. ```csharp namespace Darkmatter.Core.Contracts.Features.Paper; -public interface IPaperRig { - Camera ArtCamera { get; } // offscreen, targetTexture = Surface - RenderTexture Surface { get; } // 2048×2048 ARGB32; the paper itself - Transform PaperRoot { get; } // parent of regions/pieces/paper bg - Vector2 DesignSize { get; } // world units, e.g. (20, 20) - Rect DesignRect { get; } // centered on origin, DesignSize wide -} - -public interface IArtInputBridge { - // Converts a screen-space pointer (Input System) to art-world coords - // inside the RT. Returns false if the pointer is outside the RawImage. - bool TryScreenToArtWorld(Vector2 screenPos, out Vector2 artWorldPos); +public interface IPaperSurface { + RectTransform Root { get; } // PaperPanel — parent of slots/pieces/regions + RectTransform SlotsParent { get; } // parent for slot Images + RectTransform PiecesParent { get; } // parent for piece Images + RectTransform RegionsParent { get; } // parent for region Images + float DesignHalfSize { get; } // half of the reference square (e.g. 1024) } ``` -- `IPaperRig` is implemented by `PaperRig : MonoBehaviour` in the ColorBook scene. -- `IArtInputBridge` does the screen → RawImage local → UV → `ArtCamera.ViewportToWorldPoint` chain. -- All consumers (Coloring, ShapeBuilder, Capture, particle effects) read these from DI; they never touch `Screen.width/height` directly. +- Implemented by `PaperSurface : MonoBehaviour` in the ColorBook scene (sits on the `PaperPanel` GameObject). +- All paper-side features (`Coloring`, `ShapeBuilder`, `Capture`) parent their UI under one of these `RectTransform` slots and use canvas-local coords throughout. +- No `IPaperRig`. No `IArtInputBridge`. Input runs through Unity's `EventSystem` directly on the UI children. ### History @@ -444,7 +471,7 @@ public interface IUndoStack { ### Gallery & Capture -> `IGalleryService` is a Service contract → `Darkmatter.Core.Contracts.Services.Gallery`. `SavedArtworkDTO` is a runtime data struct → `Darkmatter.Core.Data.Dynamic.Features.Gallery`. `ICaptureService` → `Darkmatter.Core.Contracts.Services.Capture`. +> `IGalleryService` is a Service contract → `Darkmatter.Core.Contracts.Services.Gallery`. `SavedArtworkDTO` is a runtime data struct → `Darkmatter.Core.Data.Dynamic.Features.Gallery`. `ICaptureService` → `Darkmatter.Core.Contracts.Services.Capture`. `CaptureAsync` takes no args — implementation owns the `CaptureCamera` reference and renders the `PaperUI` layer to a one-shot RT. ```csharp namespace Darkmatter.Core.Data.Dynamic.Features.Gallery; @@ -469,13 +496,13 @@ public interface IGalleryService { namespace Darkmatter.Core.Contracts.Services.Capture; public interface ICaptureService { - // No camera or paperBg args — capture reads directly from IPaperRig.Surface. - // Dimensions inherited from the RT; no resize, no compositing. + // Allocates a temp RT, renders the CaptureCamera once (PaperUI layer only), + // ReadPixels into a Texture2D, encodes PNG, releases the RT. UniTask CaptureAsync(); } ``` -`ICaptureService` resolves `IPaperRig` via DI and reads `Surface` directly. The paper background is already baked into the RT because it sits in `PaperRoot` under the ArtCamera. No special compositing pass is ever needed. +`ICaptureService` owns a reference to `CaptureCamera` (a disabled `Camera` in the ColorBook scene). The capture camera's `cullingMask` is set to `PaperUI` so the HUD physically cannot appear in the PNG. The paper background is part of `PaperPanel`, so it's already in the right layer — no compositing pass. ### Signals @@ -519,25 +546,27 @@ public readonly struct ArtworkSavedSignal { ### `Paper` - Scene-scoped infrastructure. Lives in `ColorBook.unity` only. -- Owns `PaperRig` (MonoBehaviour) — exposes `ArtCamera`, the `RenderTexture Surface`, `PaperRoot` transform, and the design rect. -- Owns `ArtInputBridge` — converts pointer screen positions to art-world coords inside the RT. -- Registered in `ColorBookLifetimeScope` via `PaperRigModule`. All other features in the scene resolve `IPaperRig` / `IArtInputBridge` from DI. -- Lifetime is scene-scoped: created on scene load, destroyed on scene unload. RT is allocated in `Awake`, released in `OnDestroy`. +- Owns `PaperSurface` (MonoBehaviour) on the `PaperPanel` GameObject. Implements `IPaperSurface`, exposes `Root`, `SlotsParent`, `PiecesParent`, `RegionsParent`, `DesignHalfSize`. +- Registered in `ColorBookLifetimeScope` via `PaperSurfaceModule`. Other features resolve `IPaperSurface` from DI when they need to parent their UI under one of the role-specific `RectTransform`s. +- No render-target ownership. No input bridge. No coordinate conversion. The paper *is* the canvas children — nothing more. ### `ShapeBuilder` - Listens to `DrawingSelectedSignal`. -- Loads template via `IDrawingTemplateLoader`, parents shape pieces under `IPaperRig.PaperRoot` at off-slot positions inside the design rect. -- Per piece: drag with `ShapePieceView` (sprite + collider). Pointer events go through `IArtInputBridge.TryScreenToArtWorld`. On drop, check distance to `SlotPosition` against `SnapRadius`; if within, snap and lock. +- Loads template, spawns UI `Image` per piece under either `IPaperSurface.PiecesParent` or the HUD tray (depending on the FSM start state — usually tray). +- Each piece has `IBeginDragHandler` / `IDragHandler` / `IEndDragHandler` plus a per-piece `ShapePieceFsm`. Drag updates `RectTransform.anchoredPosition` directly from `PointerEventData` (converted to canvas-local via `RectTransformUtility.ScreenPointToLocalPointInRectangle`). +- On entering preview radius of the matching slot: reactive `Lerp` of `anchoredPosition` / `sizeDelta` / `localRotation` toward `SlotMarker`'s `RectTransform`. Drives off pointer distance, not time. +- On `OnEndDrag` inside snap radius: DOTween ease-out to exact slot pose, disable input. Otherwise DOTween back to tray slot. - Fires `ShapeAssembledSignal` when all pieces locked. ### `Coloring` - Listens to `ShapeAssembledSignal`. -- Spawns one `ColorRegionView` per `ColorRegionDTO` under `IPaperRig.PaperRoot` (sprite + polygon collider on `Artwork` layer). +- Spawns one UI `Image` per `ColorRegionDTO` under `IPaperSurface.RegionsParent`. Each region's `Image.alphaHitTestMinimumThreshold = 0.5f` so taps on transparent pixels pass through to the next region. +- Each region has `IPointerClickHandler`. On click → `ColoringController.PaintRegion(view)`. - Listens to palette selection (current color held in `ColoringStateRepository`). -- On pointer down: `IArtInputBridge.TryScreenToArtWorld(screenPos, out var artPos)` → `Physics2D.OverlapPoint(artPos, artworkMask)` → if hit, build `PaintRegionCommand(regionId, oldColor, newColor)`, push to `IUndoStack`. -- Command sets `SpriteRenderer.color` on undo/redo. +- Controller builds `PaintRegionCommand(regionId, oldColor, newColor)` and pushes to `IUndoStack`. +- Command sets `Image.color` on undo/redo. - Fires `ColorAppliedSignal` for SFX / sparkle effects. ### `History` @@ -550,7 +579,7 @@ public readonly struct ArtworkSavedSignal { ### `Capture` - Bound to the "Capture" button. -- Calls `ICaptureService.CaptureAsync()` → PNG bytes. Capture reads `IPaperRig.Surface` directly; no camera or paper-bg args needed. +- Calls `ICaptureService.CaptureAsync()` → PNG bytes. Implementation owns the disabled `CaptureCamera`, sets its `targetTexture` to a temp RT, calls `Render()` once, reads pixels, releases. - Hands bytes to `IGalleryService.SaveAsync(...)`. - Fires `ArtworkCapturedSignal` then `ArtworkSavedSignal`. - Shows a quick "saved!" toast with a thumbnail of the new entry. @@ -656,7 +685,7 @@ persistentDataPath/Gallery/ ## 12. Capture Pipeline -With the RT-paper-rig, capture has no setup phase. The RT is already the final image at all times. +A dedicated `CaptureCamera` lives in the scene, disabled by default. It renders only the `PaperUI` layer into a temp `RenderTexture` when capture fires. ``` [Capture button or Next button] @@ -664,13 +693,17 @@ With the RT-paper-rig, capture has no setup phase. The RT is already the final i ▼ ICaptureService.CaptureAsync() │ - ├─ rt = _paperRig.Surface (already populated each frame) + ├─ rt = RenderTexture.GetTemporary(2048, 2048, 0, ARGB32) + ├─ _captureCam.targetTexture = rt + ├─ _captureCam.Render() (one-shot; cullingMask = PaperUI only) + ├─ _captureCam.targetTexture = null ├─ prev = RenderTexture.active ├─ RenderTexture.active = rt - ├─ tex = new Texture2D(rt.width, rt.height, RGBA32, false) + ├─ tex = new Texture2D(2048, 2048, RGBA32, false) ├─ tex.ReadPixels(full rect, 0, 0); tex.Apply() ├─ RenderTexture.active = prev - ├─ bytes = tex.EncodeToPNG() (on worker via UniTask.RunOnThreadPool) + ├─ RenderTexture.ReleaseTemporary(rt) + ├─ bytes = tex.EncodeToPNG() (on worker via UniTask.RunOnThreadPool) ├─ Object.Destroy(tex) └─ return bytes ▼ @@ -686,10 +719,11 @@ EventBus.Publish(new ArtworkSavedSignal(dto)) Notes: -- HUD never appears in capture because the HUD is on `UICamera` / Canvas — it is physically in a different render path. The RT only ever sees `ArtCamera`'s output. -- Paper background is a sprite parented under `IPaperRig.PaperRoot` and is rendered into the RT every frame — already baked in. -- Saved PNGs are byte-comparable across devices because the RT dimensions and ArtCamera matrix never depend on screen size. -- `CaptureAsync` is safe to call repeatedly — no camera state is ever mutated. +- HUD never appears in capture because `CaptureCamera.cullingMask` excludes `HUDUI`. Layer mask, not coincidence — even if you accidentally parent a HUD element under `PaperPanel`, putting it on the wrong layer keeps it out. +- Paper background is just an `Image` on `PaperUI`. Already in the right layer; no special compositing. +- Saved PNGs are 2048×2048 on every device. `CaptureCamera` has fixed `orthographicSize` and aspect, independent of screen size. +- `CaptureAsync` is safe to call repeatedly. The CaptureCamera's transform / projection are set once at scene start and never modified. +- The temp RT is allocated via `RenderTexture.GetTemporary` so successive captures don't leak GPU memory. --- @@ -733,7 +767,7 @@ These shape several design decisions and are **non-negotiable**: - **No fail states.** Drawings cannot be "wrong". - **No timers.** Nothing decays or runs out. -- **No tiny hitboxes.** Drag tolerance ≥ 40 px; snap radius ≥ 60 px for shape pieces. +- **No tiny hitboxes.** Drag tolerance ≥ 40 canvas units; snap radius ≥ 80 canvas units for shape pieces. (Canvas reference resolution is 2048×2048 — these are anchored-position deltas, not screen px.) - **Auto-snap on near-miss.** If a piece is dropped within `1.5 × SnapRadius`, snap anyway and play a happy sound. - **No text-heavy UI.** Icons everywhere. Single-word labels max. - **Loud, immediate feedback.** Every tap plays a sound; every fill bursts a small particle effect. @@ -1101,48 +1135,67 @@ Same shape repeats for every feature's UI. ## 26. ShapeBuilder — Snap Algorithm -```csharp -// In ShapePieceView.OnPointerUp: -public void OnDragEnd(Vector2 worldPos) { - var slot = transform.position; // assigned target slot - var d = Vector2.Distance(worldPos, slot); +All math is in canvas-local space — `anchoredPosition`, `sizeDelta`, `localRotation`. No world coords. - if (d <= _piece.SnapRadius) { +```csharp +// In ShapePieceFsm.OnDragEnd (state: Dragging or Preview): +public void OnDragEnd() { + var pieceRT = _ui.RectTransform; + var slotRT = _targetSlot.RectTransform; + var d = Vector2.Distance(pieceRT.anchoredPosition, slotRT.anchoredPosition); + + if (d <= _cfg.SnapRadius) { SnapToSlot(); - } else if (d <= _piece.SnapRadius * 1.5f) { + } else if (d <= _cfg.SnapRadius * 1.5f) { // Toddler grace zone — snap anyway, play happy sound SnapToSlot(); - _audio.PlayOneShot(_clips.NiceTry); + _audio.PlayOneShot(SfxId.NiceTry); } else { ReturnToTrayAnimated(); } } private void SnapToSlot() { - _locked = true; - transform.DOMove(_piece.SlotPosition, 0.25f).SetEase(Ease.OutBack); - _audio.PlayOneShot(_clips.Snap); - _bus.Publish(new PieceSnappedSignal(_piece.PieceId)); + _ui.RectTransform.SetParent(_paper.PiecesParent, worldPositionStays: false); + var slot = _targetSlot.RectTransform; + _ui.RectTransform.DOAnchorPos(slot.anchoredPosition, 0.25f).SetEase(Ease.OutBack); + _ui.RectTransform.DOSizeDelta(slot.sizeDelta, 0.25f).SetEase(Ease.OutBack); + _ui.RectTransform.DOLocalRotateQuaternion(slot.localRotation, 0.25f).SetEase(Ease.OutBack); + _audio.PlayOneShot(SfxId.Snap); + _bus.Publish(new PieceSnappedSignal(_ui.PieceId)); } ``` +Three things to note: + +1. **Reparent** the piece from `TrayPanel` (HUD canvas) to `IPaperSurface.PiecesParent` (PaperCanvas) so it'll be included in capture. `worldPositionStays: false` because we want the new `anchoredPosition` to be relative to the new parent, not the world. +2. **Three simultaneous tweens** — position, size, rotation. Use `DOAnchorPos`, `DOSizeDelta`, `DOLocalRotateQuaternion`. They start together so the piece visually snaps as one motion. +3. **`SnapRadius` is in canvas units** (from `ShapeBuilderConfig`, e.g. 80–120), not world units. Same `CanvasScaler` reference resolution across devices = same hit feel. + Controller listens for `PieceSnappedSignal`, counts against expected piece count, fires `ShapeAssembledSignal` when complete. --- ## 27. Rendering Order & Sorting -URP 2D with a single `ArtCamera` ortho cam. +Canvas-only — order is sibling index inside `PaperPanel` (front-most is last in hierarchy). No URP 2D sorting layers. -| Sorting Layer | Order | Contents | -|---|---|---| -| `PaperBackground` | 0 | Paper bg sprite (under everything) | -| `ArtworkRegions` | 100 | `ColorRegionView` sprites (the colorable shapes) | -| `ArtworkPieces` | 200 | `ShapePieceView` sprites (during build) | -| `Effects` | 300 | Particle bursts, sparkles | -| `UIWorld` | 400 | World-space prompts (rare; mostly Canvas) | +`PaperPanel` children (bottom → top): -Canvas HUD lives on `UICamera` (Overlay), never sorts against `ArtCamera`. Capture renders only `ArtCamera`'s layers → HUD physically cannot leak into saved PNG. +``` +PaperPanel +├─ BackgroundImage (paper texture) +├─ RegionsPanel (colorable region Images) +├─ SlotsPanel (slot outline Images — under pieces so snapped pieces hide them) +├─ PiecesPanel (draggable / snapped piece Images) +└─ EffectsPanel (sparkle / particle UI for completion FX, optional) +``` + +`HUDCanvas` is a separate Canvas at a higher sorting order (or Screen Space - Overlay). It never sorts against `PaperCanvas` because they're different canvases. + +`CaptureCamera` renders only the `PaperUI` layer. The HUD physically cannot leak into the saved PNG because of the culling mask, regardless of sibling order. + +> If you ever need particles outside the canvas (e.g. confetti falling across the full screen on completion), use a separate Canvas above the HUD with its own sub-tree of UI particles. Don't add `ParticleSystem`s under PaperPanel — they don't render in UI canvases without `UIParticleSystem` or similar packages. --- @@ -1209,13 +1262,13 @@ Toddler-mode error UI: | Class | Layer | Asmdef | |---|---|---| | `IDrawingTemplate`, `ShapePieceDTO`, `ColorRegionDTO` | Core | `Core` | -| `IPaperRig`, `IArtInputBridge` | Core | `Core` | +| `IPaperSurface` | Core | `Core` | | `ICommand`, `IUndoStack` | Core | `Core` | | `BoundedUndoStack` | Libs | `Libs.CommandStack` | | `AddressableAssetProviderService` | Services | `Services.Assets` | | `FileGalleryService` | Services | `Services.Gallery` | | `RenderTextureCaptureService` | Services | `Services.Capture` | -| `PaperRig`, `ArtInputBridge`, `PaperRigModule` | Features | `Features.Paper` | +| `PaperSurface`, `PaperSurfaceModule` | Features | `Features.Paper` | | `ColoringController`, `PaintRegionCommand` | Features | `Features.Coloring` | | `ShapeBuilderController`, `ShapePieceView` | Features | `Features.ShapeBuilder` | | `HistoryController` | Features | `Features.History` | @@ -1305,26 +1358,18 @@ public interface ICaptureService { } ``` -#### `IPaperRig` *(Core/Contracts/Features/Paper — planned)* -Shared art rig. The single source of truth for everything that lives in the drawing world. +#### `IPaperSurface` *(Core/Contracts/Features/Paper — planned)* +The paper is just RectTransform real estate. Features parent their UI children under one of the role-specific roots. ```csharp -public interface IPaperRig { - Camera ArtCamera { get; } // offscreen, targetTexture = Surface - RenderTexture Surface { get; } // 2048×2048 ARGB32 — the paper itself - Transform PaperRoot { get; } // parent of regions/pieces/paper bg - Vector2 DesignSize { get; } // world units, e.g. (20, 20) - Rect DesignRect { get; } // centered on origin +public interface IPaperSurface { + RectTransform Root { get; } // PaperPanel itself + RectTransform SlotsParent { get; } // child of Root — for ShapeBuilder slot outlines + RectTransform PiecesParent { get; } // child of Root — for ShapeBuilder pieces (post-snap) + RectTransform RegionsParent { get; } // child of Root — for Coloring region Images + float DesignHalfSize { get; } // half the reference resolution side, in canvas units } ``` - -#### `IArtInputBridge` *(Core/Contracts/Features/Paper — planned)* -Converts screen-space pointer coords to art-world coords inside the RT. -```csharp -public interface IArtInputBridge { - bool TryScreenToArtWorld(Vector2 screenPos, out Vector2 artWorldPos); -} -``` -Returns `false` when the pointer is outside the displayed RawImage rect (toddler tapped the HUD or backdrop). Every art-world raycast goes through this. +No render-target ownership. No coordinate conversion. The contract just hands out RectTransforms so features don't have to `Find` them. #### `IProgressionService` *(Core/Contracts/Features/Progression — planned)* Tracks which templates the child has completed and what they last opened. @@ -1386,9 +1431,16 @@ Implements `IGalleryService`. - **Delete flow:** delete png + thumb + json; missing files ignored (idempotent). #### `RenderTextureCaptureService` *(Services/Capture — planned)* -Implements `ICaptureService`. -- **Steps:** allocate `RenderTexture(width, height, 0, ARGB32)` → bind to `artCamera.targetTexture` → `artCamera.Render()` → `ReadPixels` into `Texture2D` → composite `paperBackground` underneath (single shader blit) → `EncodeToPNG` → release RT + textures. -- **Threading:** PNG encode happens on a `UniTask.RunOnThreadPool` to avoid hitching the main thread on tablets. +Implements `ICaptureService`. Drives the scene's disabled `CaptureCamera` once per capture. +```csharp +// fields: +// Camera _captureCam (scene-bound, registered via CaptureServiceModule) +// int _surfaceSize = 2048 +// IPathProvider _paths (only if you want to expose paths — usually not needed here) +``` +- **Steps:** `RenderTexture.GetTemporary(size, size, 0, ARGB32)` → set `_captureCam.targetTexture = rt` → `_captureCam.Render()` → `ReadPixels` into a `Texture2D` → null out the target texture → `RenderTexture.ReleaseTemporary(rt)` → `EncodeToPNG` → return bytes. +- **Threading:** PNG encode happens on `UniTask.RunOnThreadPool` to avoid hitching the main thread on tablets. +- **Camera setup:** `_captureCam` has `cullingMask = PaperUI`, `clearFlags = SolidColor` (white or paper color), `orthographicSize` and `aspect` cloned from `UICamera` once at scene start. Stays disabled — `Render()` is the only call site. - **Sizing:** default 2048², overridable. Capped at device max texture size. #### `JsonPersistenceService` *(Services/Persistence — planned; today `Libs/PlayerPrefs` covers small-key state)* @@ -1508,9 +1560,10 @@ public interface IDrawingCatalogView { #### `ShapeBuilderController` *(Systems)* Spawns shape pieces for the selected template, tracks snap progress, fires `ShapeAssembledSignal` when complete. ```csharp -// fields: IDrawingTemplateCatalog _catalog, ShapePieceFactory _factory, IEventBus _bus, ShapeBuilderConfig _cfg +// fields: IDrawingTemplateCatalog _catalog, ShapePieceFactory _factory, +// IPaperSurface _paper, TrayPanel _tray, IEventBus _bus, ShapeBuilderConfig _cfg public sealed class ShapeBuilderController : IDisposable { - public IReadOnlyList Active { get; } + public IReadOnlyList Active { get; } public UniTask BuildAsync(string templateId); // load template, spawn pieces in tray public void Reset(); // clear, unsubscribe } @@ -1519,97 +1572,101 @@ public sealed class ShapeBuilderController : IDisposable { ``` - **Internal:** counts `PieceSnappedSignal` against expected piece count. -#### `ShapePieceView : MonoBehaviour` *(Views)* -World-space draggable sprite with collider. Source for snap-or-return logic shown in section 26. +#### `ShapePieceUI : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler` *(UI)* +The UI Image that the toddler drags. Lives under `TrayPanel` while idle, reparents under `IPaperSurface.PiecesParent` when snapped. ```csharp -public sealed class ShapePieceView : MonoBehaviour { +public sealed class ShapePieceUI : MonoBehaviour, + IBeginDragHandler, IDragHandler, IEndDragHandler +{ public string PieceId { get; } public bool IsLocked { get; } - public event Action Snapped; // raised when piece locks into slot - public void Initialize(ShapePieceDTO dto, IInputReader input, IAudioService audio); + public RectTransform RectTransform { get; } + public event Action Snapped; + + public void Initialize(ShapePieceDTO dto, ShapePieceFsm fsm); } ``` -- **No public mutators** for position once locked — controller treats `IsLocked` as the source of truth. +- Handlers forward to `ShapePieceFsm` (`OnDragBegin / OnDrag(localPos) / OnDragEnd`). +- `OnDrag` converts `PointerEventData.position` to canvas-local via `RectTransformUtility.ScreenPointToLocalPointInRectangle` against the piece's parent rect. +- No collider, no Physics2D anywhere. + +#### `ShapePieceFsm` *(Systems)* +Per-piece state machine using `Libs.FSM`. States: `InTray → Dragging → Preview → (Snapped | Returning)`. +```csharp +// fields: ShapePieceUI _ui, SlotMarker _targetSlot, ShapeBuilderConfig _cfg, +// IAudioService _audio, IEventBus _bus +public sealed class ShapePieceFsm { + public void OnDragBegin(); + public void OnDrag(Vector2 canvasLocalPos); + public void OnDragEnd(); + public bool IsLocked { get; } +} +``` +- **Preview-state update**: reactive lerp of `anchoredPosition / sizeDelta / localRotation` toward `_targetSlot`'s pose, driven by `1 - dist/PreviewRadius`. No DOTween while previewing — it's per-frame. +- **Snapped enter**: DOTween ease-out to exact slot pose (~0.2s), disable drag, fire `PieceSnappedSignal`. +- **Returning enter**: DOTween back to tray slot (`anchoredPosition` from `TrayLayout`). + +#### `SlotMarker : MonoBehaviour` *(UI)* +The outline `Image` on `IPaperSurface.SlotsParent` showing where a piece should snap. Its `RectTransform` directly *is* the target pose — `ShapePieceFsm` reads `anchoredPosition`, `sizeDelta`, `localRotation` from it. +```csharp +public sealed class SlotMarker : MonoBehaviour { + public string SlotId; + public RectTransform RectTransform => transform as RectTransform; +} +``` + +#### `TrayPanel : MonoBehaviour` *(UI)* +HUD-side panel (on `HUDCanvas`) where pieces start out. Has a `HorizontalLayoutGroup` + `ContentSizeFitter`. Provides spawn anchors via `RectTransform Slot(int index)` for the controller. #### `ShapePieceFactory` *(Systems)* -Instantiates `ShapePieceView` prefabs from a pool. Avoids re-instantiating across "Next" cycles on the same template family. +Pool of `ShapePieceUI` GameObjects + their associated FSMs. Reused across template loads. ```csharp public sealed class ShapePieceFactory { - public ShapePieceView Spawn(ShapePieceDTO dto, Transform parent); - public void Despawn(ShapePieceView view); + public ShapePieceUI Spawn(ShapePieceDTO dto, RectTransform parent); + public void Despawn(ShapePieceUI piece); } ``` +#### `ShapeBuilderInputBinder` *(Systems)* +With UI handlers on the piece itself, an explicit input binder isn't strictly needed — drag events route via the EventSystem. Keep this class only if you need to listen for "any tap outside any piece" (e.g. to dismiss a preview). Otherwise skip. + --- ### 32.5b Feature — `Paper` *(planned)* -The shared art rig — RT, offscreen camera, screen↔world bridge. Every other feature in the ColorBook scene resolves `IPaperRig` and `IArtInputBridge` from DI and never touches `Screen.*` or `Camera.*` directly. +A tiny feature. Just exposes the paper RectTransforms via DI so consumers don't `Find` them. -#### `PaperRig : MonoBehaviour, IPaperRig` *(Rig)* -Scene-bound component placed on a GameObject in `ColorBook.unity`. Owns the RT lifecycle. +#### `PaperSurface : MonoBehaviour, IPaperSurface` *(Surface)* +Scene-bound component placed on the `PaperPanel` GameObject in `ColorBook.unity`. ```csharp // inspector fields: -// Camera _artCamera (Orthographic, aspect=1, fixed ortho size) -// Transform _paperRoot (parent of regions/pieces) -// Vector2 _designSize = (20, 20) (world units; matches 2048×2048 at PPU=100) -// int _surfaceSize = 2048 (RT side length, square) +// RectTransform _slotsParent +// RectTransform _piecesParent +// RectTransform _regionsParent +// float _designHalfSize = 1024f // half of 2048 reference resolution -public sealed class PaperRig : MonoBehaviour, IPaperRig { - public Camera ArtCamera => _artCamera; - public RenderTexture Surface => _surface; - public Transform PaperRoot => _paperRoot; - public Vector2 DesignSize => _designSize; - public Rect DesignRect => new(-_designSize / 2f, _designSize); +public sealed class PaperSurface : MonoBehaviour, IPaperSurface { + public RectTransform Root => (RectTransform)transform; + public RectTransform SlotsParent => _slotsParent; + public RectTransform PiecesParent => _piecesParent; + public RectTransform RegionsParent => _regionsParent; + public float DesignHalfSize => _designHalfSize; } ``` -- **Awake:** allocate `_surface = new RenderTexture(_surfaceSize, _surfaceSize, 0, ARGB32) { name = "PaperSurface" };` then `_surface.Create()` and `_artCamera.targetTexture = _surface; _artCamera.aspect = 1f; _artCamera.orthographicSize = _designSize.y / 2f;`. -- **OnDestroy:** `_surface.Release(); Object.Destroy(_surface);`. -- **No update logic** — the camera renders every frame automatically because `targetTexture` is set. -- **Important:** `_artCamera`'s `orthographicSize` and `aspect` are set once and never touched again. The RT contents are deterministic. +- No `Awake` / `OnDestroy` logic. The component is a pure pass-through to the RectTransforms. +- All four child rects share the same anchors and size as `Root` (anchored center, stretched to fill). -#### `ArtInputBridge : MonoBehaviour, IArtInputBridge` *(Input)* -Lives on the same UI Canvas as the paper `RawImage`. +#### `PaperSurfaceModule : MonoBehaviour, IServiceModule` *(Installers)* +Scene-scoped installer. Dragged into `ColorBookLifetimeScope.sceneModules[]`. ```csharp // inspector fields: -// RawImage _paperImage (the on-screen paper) -// RectTransform _paperRect (== _paperImage.rectTransform) -// Camera _uiCamera (Canvas event camera) -// IPaperRig _rig (injected via VContainer + IInjectable, or resolved in Start) - -public bool TryScreenToArtWorld(Vector2 screenPos, out Vector2 artWorldPos) { - if (!RectTransformUtility.ScreenPointToLocalPointInRectangle( - _paperRect, screenPos, _uiCamera, out var local)) { - artWorldPos = default; return false; - } - var rect = _paperRect.rect; - var uv = new Vector2( - (local.x - rect.xMin) / rect.width, - (local.y - rect.yMin) / rect.height); - if (uv.x < 0 || uv.x > 1 || uv.y < 0 || uv.y > 1) { - artWorldPos = default; return false; - } - artWorldPos = _rig.ArtCamera.ViewportToWorldPoint(uv); - return true; -} -``` -- Returns `false` when the toddler tapped outside the RawImage (HUD button area, backdrop, off-screen). -- Used by every feature that does world-space picking — `Coloring`, `ShapeBuilder`, and any future feature like stickers. - -#### `PaperRigModule : MonoBehaviour, IServiceModule` *(Installers)* -Scene-scoped installer. Dragged onto `ColorBookLifetimeScope._installers[]`. -```csharp -// inspector fields: -// PaperRig _rig -// ArtInputBridge _bridge +// PaperSurface _surface public void Register(IContainerBuilder builder) { - builder.RegisterInstance(_rig); - builder.RegisterInstance(_bridge); + builder.RegisterInstance(_surface); } ``` -- Registers as `Instance` because both are MonoBehaviours already in the scene. -- Lifetime is implicitly tied to the scene (Unity destroys them on unload). +Registers as `Instance` because `PaperSurface` is a MonoBehaviour already in the scene. Lifetime tied to the scene. --- @@ -1632,7 +1689,8 @@ public sealed class ColoringStateRepository { #### `ColoringController` *(Systems)* — implements `IColoringController` Builds and pushes `PaintRegionCommand` instances; spawns `ColorRegionView` per region. ```csharp -// fields: IUndoStack _undo, ColoringStateRepository _state, ColorRegionFactory _factory, IEventBus _bus +// fields: IUndoStack _undo, ColoringStateRepository _state, ColorRegionFactory _factory, +// IPaperSurface _paper, IEventBus _bus public interface IColoringController { UniTask SpawnRegionsAsync(IDrawingTemplate template); void PaintRegion(ColorRegionView view); // builds command, pushes to undo stack @@ -1641,28 +1699,23 @@ public interface IColoringController { // sub: ShapeAssembledSignal (via flow controller, not direct) // pub: ColorAppliedSignal (via PaintRegionCommand) ``` +Spawns each region as a UI `Image` under `_paper.RegionsParent`. No `Physics2D`. -#### `ColorRegionView : MonoBehaviour` *(Views)* -Sprite + `PolygonCollider2D`, on `Artwork` layer. Tapped via `Physics2D.OverlapPoint` from `ColoringInputBinder`. +#### `ColorRegionView : MonoBehaviour, IPointerClickHandler` *(UI)* +UI Image with alpha-based hit detection. Tap routes through Unity's EventSystem directly to `OnPointerClick`. ```csharp -public sealed class ColorRegionView : MonoBehaviour { +public sealed class ColorRegionView : MonoBehaviour, IPointerClickHandler { public string RegionId { get; } - public Color Color { get; } // current paint - public void Initialize(ColorRegionDTO dto); + public Color Color => _image.color; + public void Initialize(ColorRegionDTO dto, IColoringController controller); public void SetColor(Color c); // setter only; no logic + public void OnPointerClick(PointerEventData e) => _controller.PaintRegion(this); } ``` +- **Required sprite setup:** sprite import inspector → **Read/Write Enabled = on**, **Generate Physics Shape = off** (not needed). `Image.alphaHitTestMinimumThreshold = 0.5f` on Initialize so taps on transparent pixels pass through to the next region below. +- **Sibling order matters** for stacked regions — top sibling gets first crack at the click; with alpha hit-test, transparent areas defer correctly. -#### `ColoringInputBinder` *(Systems)* — `IStartable, IDisposable` -Subscribes to `IInputReader.PointerDown`. On each tap: -1. `_bridge.TryScreenToArtWorld(screenPos, out var artPos)` — bail if outside the paper. -2. `Physics2D.OverlapPoint(artPos, _artworkMask)` against the `Artwork` layer. -3. If hit, `ColoringController.PaintRegion(hit.GetComponent())`. - -```csharp -// fields: IInputReader _input, IArtInputBridge _bridge, IColoringController _coloring, LayerMask _artworkMask -``` -Note: `_bridge` is the same instance the entire scene uses — no per-feature coordinate math. +No `ColoringInputBinder` class needed. Unity's EventSystem fires `OnPointerClick` on the topmost UI element under the pointer that consumes it — exactly what we want. #### `PaintRegionCommand` *(Commands)* Source in section 23. Holds `view`, `fromColor`, `toColor`, `bus`. Symmetrical execute/undo. @@ -1721,7 +1774,7 @@ public sealed class CaptureController { ``` - **Flow:** `_capture.CaptureAsync()` → `_gallery.SaveAsync(bytes, templateId)` → publish signals. - **Concurrency:** sets `IsCapturing = true` on entry; UI binds button enabled to `!IsCapturing` to prevent double-tap. -- **No camera or sprite args** — capture reads `IPaperRig.Surface` directly inside the service. +- **No camera or sprite args** — the implementation owns a reference to the disabled `CaptureCamera` and drives the one-shot render internally. #### `CaptureButtonPresenter` *(UI)* Wires button click → `CaptureController.CaptureCurrentAsync`. Disables button while in progress. Shows toast on `ArtworkSavedSignal`. @@ -1826,10 +1879,10 @@ All scope classes are thin: a serialized installer-MonoBehaviour list (+ optiona ### 32.13 Cross-cutting types -#### `ColorBookSceneRefs : MonoBehaviour` *(App — planned)* -Aggregates scene-bound Unity references that features need: `Camera artCamera`, `Transform catalogRoot`, `Transform builderRoot`, `Transform coloringRoot`, `RectTransform hudRoot`, `ColorPaletteView paletteView`, `HistoryButtonsView historyView`. Registered in `ColorBookLifetimeScope` via `builder.RegisterInstance(_sceneRefs)` so features don't `Find` things. +#### `ColorBookSceneRefs : MonoBehaviour` *(App — planned, optional)* +Aggregates HUD-side scene-bound Unity references that don't fit any single feature. Examples: `Camera captureCamera`, `RectTransform hudRoot`, `ColorPaletteView paletteView`, `HistoryButtonsView historyView`, `TrayPanel trayPanel`. Registered in `ColorBookLifetimeScope` via `builder.RegisterInstance(_sceneRefs)` so features don't `Find` things. -> Most of these refs are subsumed by `IPaperRig` now (which owns `ArtCamera` and `PaperRoot`). `ColorBookSceneRefs` reduces to the HUD-side refs (palette view, history buttons, panel roots). +> Paper-side refs are subsumed by `IPaperSurface` (which exposes the four canvas RectTransform roots). `CaptureCamera` could either live here or be exposed via its own dedicated `ICaptureCameraSource` contract — for v1, putting it on `ColorBookSceneRefs` is fine. #### `IServiceModule` *(Libs/Installers — ✅ exists)* ```csharp @@ -1850,15 +1903,17 @@ Implemented as `MonoBehaviour` per feature/service so scopes can drag them in th | `ColorBookLifetimeScope` | App | Scene DI | scene refs, installers | | `DrawingCatalogController` | Feature | Grid logic | catalog, bus | | `DrawingCatalogPresenter` | Feature | UI bridge | view, controller, catalog | -| `ShapeBuilderController` | Feature | Piece spawn + snap tracking | catalog, factory, bus, cfg | -| `ShapePieceView` | Feature | Draggable piece MB | input, audio | +| `ShapeBuilderController` | Feature | Piece spawn + snap tracking | catalog, factory, paper, tray, bus, cfg | +| `ShapePieceUI` | Feature | Draggable UI piece (Image + drag handlers) | fsm | +| `ShapePieceFsm` | Feature | Per-piece state machine | ui, slot, cfg, audio, bus | +| `SlotMarker` | Feature | Slot outline UI Image at target pose | — | +| `TrayPanel` | Feature | HUD-side tray with LayoutGroup | — | | `ColoringStateRepository` | Feature | Current color model | — | -| `ColoringController` | Feature | Region spawn + paint cmd | undo, state, factory, bus | -| `ColorRegionView` | Feature | Region sprite MB | — | -| `PaintRegionCommand` | Feature | Undoable paint | view, bus | -| `PaperRig` | Feature | RT + ArtCamera owner | — | -| `ArtInputBridge` | Feature | Screen→art-world picking | rig, raw image, ui cam | -| `PaperRigModule` | Feature | DI registration | rig, bridge | +| `ColoringController` | Feature | Region spawn + paint cmd | undo, state, factory, paper, bus | +| `ColorRegionView` | Feature | Region UI Image + IPointerClickHandler | controller | +| `PaintRegionCommand` | Feature | Undoable paint (sets Image.color) | view, bus | +| `PaperSurface` | Feature | IPaperSurface (Root + child rects) | — | +| `PaperSurfaceModule` | Feature | DI registration | surface | | `HistoryController` | Feature | Undo/redo facade | undo stack, bus | | `CaptureController` | Feature | Capture+save orchestration | capture svc, gallery, bus | | `ColorBookFlowController` | Feature | Scene FSM | bus, catalog, builder, coloring, capture, progression |