diff --git a/.DS_Store b/.DS_Store index 5860754..8eab601 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/Assets/Darkmatter/Scenes/GamePlay.unity b/Assets/Darkmatter/Scenes/GamePlay.unity index 2f8a6d2..b35ddb3 100644 --- a/Assets/Darkmatter/Scenes/GamePlay.unity +++ b/Assets/Darkmatter/Scenes/GamePlay.unity @@ -119,6 +119,85 @@ NavMeshSettings: debug: m_Flags: 0 m_NavMeshData: {fileID: 0} +--- !u!1 &590523272 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 590523275} + - component: {fileID: 590523274} + - component: {fileID: 590523273} + m_Layer: 0 + m_Name: EventSystem + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &590523273 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 590523272} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 01614664b831546d2ae94a42149d80ac, type: 3} + m_Name: + m_EditorClassIdentifier: Unity.InputSystem::UnityEngine.InputSystem.UI.InputSystemUIInputModule + m_SendPointerHoverToParent: 1 + m_MoveRepeatDelay: 0.5 + m_MoveRepeatRate: 0.1 + m_XRTrackingOrigin: {fileID: 0} + m_ActionsAsset: {fileID: -944628639613478452, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3} + m_PointAction: {fileID: -1654692200621890270, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3} + m_MoveAction: {fileID: -8784545083839296357, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3} + m_SubmitAction: {fileID: 392368643174621059, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3} + m_CancelAction: {fileID: 7727032971491509709, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3} + m_LeftClickAction: {fileID: 3001919216989983466, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3} + m_MiddleClickAction: {fileID: -2185481485913320682, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3} + m_RightClickAction: {fileID: -4090225696740746782, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3} + m_ScrollWheelAction: {fileID: 6240969308177333660, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3} + m_TrackedDevicePositionAction: {fileID: 6564999863303420839, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3} + m_TrackedDeviceOrientationAction: {fileID: 7970375526676320489, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3} + m_DeselectOnBackgroundClick: 1 + m_PointerBehavior: 0 + m_CursorLockBehavior: 0 + m_ScrollDeltaPerTick: 6 +--- !u!114 &590523274 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 590523272} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 76c392e42b5098c458856cdf6ecaaaa1, type: 3} + m_Name: + m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.EventSystems.EventSystem + m_FirstSelected: {fileID: 0} + m_sendNavigationEvents: 1 + m_DragThreshold: 10 +--- !u!4 &590523275 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 590523272} + 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: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &619394800 GameObject: m_ObjectHideFlags: 0 @@ -331,9 +410,186 @@ Transform: m_Children: [] m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &2069155637 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2069155641} + - component: {fileID: 2069155640} + - component: {fileID: 2069155639} + - component: {fileID: 2069155638} + m_Layer: 5 + m_Name: Canvas + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &2069155638 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2069155637} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3} + m_Name: + m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.GraphicRaycaster + m_IgnoreReversedGraphics: 1 + m_BlockingObjects: 0 + m_BlockingMask: + serializedVersion: 2 + m_Bits: 4294967295 +--- !u!114 &2069155639 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2069155637} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3} + m_Name: + m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.CanvasScaler + m_UiScaleMode: 0 + m_ReferencePixelsPerUnit: 100 + m_ScaleFactor: 1 + m_ReferenceResolution: {x: 800, y: 600} + m_ScreenMatchMode: 0 + m_MatchWidthOrHeight: 0 + m_PhysicalUnit: 3 + m_FallbackScreenDPI: 96 + m_DefaultSpriteDPI: 96 + m_DynamicPixelsPerUnit: 1 + m_PresetInfoIsWorld: 0 +--- !u!223 &2069155640 +Canvas: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2069155637} + m_Enabled: 1 + serializedVersion: 3 + m_RenderMode: 0 + m_Camera: {fileID: 0} + m_PlaneDistance: 100 + m_PixelPerfect: 0 + m_ReceivesEvents: 1 + m_OverrideSorting: 0 + m_OverridePixelPerfect: 0 + m_SortingBucketNormalizedSize: 0 + m_VertexColorAlwaysGammaSpace: 0 + m_UseReflectionProbes: 0 + m_AdditionalShaderChannelsFlag: 0 + m_UpdateRectTransformForStandalone: 0 + m_SortingLayerID: 0 + m_SortingOrder: 0 + m_TargetDisplay: 0 +--- !u!224 &2069155641 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2069155637} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 0, y: 0, z: 0} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 2081960987} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + 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} diff --git a/Assets/Settings.meta b/Assets/Settings.meta new file mode 100644 index 0000000..b36ca5e --- /dev/null +++ b/Assets/Settings.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0220aab0833d04faeb927d84ca6cc40c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Settings/Build Profiles.meta b/Assets/Settings/Build Profiles.meta new file mode 100644 index 0000000..c5175ef --- /dev/null +++ b/Assets/Settings/Build Profiles.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b27be038024594dd39317bb67b36c5a9 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Settings/Build Profiles/Android™.asset b/Assets/Settings/Build Profiles/Android™.asset new file mode 100644 index 0000000..ec5fbe8 --- /dev/null +++ b/Assets/Settings/Build Profiles/Android™.asset @@ -0,0 +1,57 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 15003, guid: 0000000000000000e000000000000000, type: 0} + m_Name: "Android\u2122" + m_EditorClassIdentifier: UnityEditor.dll::UnityEditor.Build.Profile.BuildProfile + m_AssetVersion: 1 + m_BuildTarget: 13 + m_Subtarget: 0 + m_PlatformId: b9b35072a6f44c2e863f17467ea3dc13 + m_PlatformBuildProfile: + rid: 570818657416118487 + m_OverrideGlobalSceneList: 0 + m_Scenes: [] + m_HasScriptingDefines: 0 + m_ScriptingDefines: [] + m_PlayerSettingsYaml: + m_Settings: [] + references: + version: 2 + RefIds: + - rid: 570818657416118487 + type: {class: AndroidPlatformBuildSettings, ns: UnityEditor.Android, asm: UnityEditor.Android.Extensions} + data: + m_Development: 0 + m_ConnectProfiler: 0 + m_BuildWithDeepProfilingSupport: 0 + m_AllowDebugging: 0 + m_WaitForManagedDebugger: 0 + m_ManagedDebuggerFixedPort: 0 + m_ExplicitNullChecks: 0 + m_ExplicitDivideByZeroChecks: 0 + m_ExplicitArrayBoundsChecks: 0 + m_CompressionType: 2 + m_InstallInBuildFolder: 0 + m_InsightsSettingsContainer: + m_BuildProfileEngineDiagnosticsState: 2 + m_AdaptivePerformanceEnabled: 0 + m_BuildSubtarget: 0 + m_BuildSystem: 1 + m_ExportAsGoogleAndroidProject: 0 + m_DebugSymbolLevel: 1 + m_DebugSymbolFormat: 5 + m_CurrentDeploymentTargetId: __builtin__target_default + m_BuildType: 2 + m_LinkTimeOptimization: 0 + m_BuildAppBundle: 0 + m_IPAddressToConnect: + m_SymlinkSources: 0 diff --git a/Assets/Settings/Build Profiles/Android™.asset.meta b/Assets/Settings/Build Profiles/Android™.asset.meta new file mode 100644 index 0000000..7e01ee7 --- /dev/null +++ b/Assets/Settings/Build Profiles/Android™.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ff2c8fd3c82954b13886250a30e34299 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Settings/Build Profiles/iOS.asset b/Assets/Settings/Build Profiles/iOS.asset new file mode 100644 index 0000000..1a19b6e --- /dev/null +++ b/Assets/Settings/Build Profiles/iOS.asset @@ -0,0 +1,50 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 15003, guid: 0000000000000000e000000000000000, type: 0} + m_Name: iOS + m_EditorClassIdentifier: UnityEditor.dll::UnityEditor.Build.Profile.BuildProfile + m_AssetVersion: 1 + m_BuildTarget: 9 + m_Subtarget: 0 + m_PlatformId: ad48d16a66894befa4d8181998c3cb09 + m_PlatformBuildProfile: + rid: 570818657416118488 + m_OverrideGlobalSceneList: 0 + m_Scenes: [] + m_HasScriptingDefines: 0 + m_ScriptingDefines: [] + m_PlayerSettingsYaml: + m_Settings: [] + references: + version: 2 + RefIds: + - rid: 570818657416118488 + type: {class: iOSPlatformSettings, ns: UnityEditor.iOS, asm: UnityEditor.iOS.Extensions} + data: + m_Development: 0 + m_ConnectProfiler: 0 + m_BuildWithDeepProfilingSupport: 0 + m_AllowDebugging: 0 + m_WaitForManagedDebugger: 0 + m_ManagedDebuggerFixedPort: 0 + m_ExplicitNullChecks: 0 + m_ExplicitDivideByZeroChecks: 0 + m_ExplicitArrayBoundsChecks: 0 + m_CompressionType: 0 + m_InstallInBuildFolder: 0 + m_InsightsSettingsContainer: + m_BuildProfileEngineDiagnosticsState: 2 + m_AdaptivePerformanceEnabled: 0 + m_iOSXcodeBuildConfig: 1 + m_SymlinkSources: 0 + m_PreferredXcode: + m_SymlinkTrampoline: 0 diff --git a/Assets/Settings/Build Profiles/iOS.asset.meta b/Assets/Settings/Build Profiles/iOS.asset.meta new file mode 100644 index 0000000..c107ddd --- /dev/null +++ b/Assets/Settings/Build Profiles/iOS.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 730617c35b3554675954ba5930be8410 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/manifest.json b/Packages/manifest.json index 5b3f093..24136f3 100644 --- a/Packages/manifest.json +++ b/Packages/manifest.json @@ -57,23 +57,25 @@ "com.unity.modules.video": "1.0.0", "com.unity.modules.vr": "1.0.0", "com.unity.modules.wind": "1.0.0", - "com.unity.modules.xr": "1.0.0" + "com.unity.modules.xr": "1.0.0", + "com.yasirkula.nativegallery": "1.9.3" }, "scopedRegistries": [ - { - "name": "package.openupm.com", - "url": "https://package.openupm.com", - "scopes": [ - "com.gilzoide.update-manager", - "jp.hadashikick.vcontainer" - ] - }, { "name": "npm", "url": "https://registry.npmjs.org", "scopes": [ "com.kyrylokuzyk" ] + }, + { + "name": "package.openupm.com", + "url": "https://package.openupm.com", + "scopes": [ + "com.gilzoide.update-manager", + "com.yasirkula.nativegallery", + "jp.hadashikick.vcontainer" + ] } ] -} +} \ No newline at end of file diff --git a/Packages/packages-lock.json b/Packages/packages-lock.json index 55f9080..397f736 100644 --- a/Packages/packages-lock.json +++ b/Packages/packages-lock.json @@ -345,6 +345,13 @@ }, "url": "https://packages.unity.com" }, + "com.yasirkula.nativegallery": { + "version": "1.9.3", + "depth": 0, + "source": "registry", + "dependencies": {}, + "url": "https://package.openupm.com" + }, "jp.hadashikick.vcontainer": { "version": "1.18.0", "depth": 0, diff --git a/ProjectSettings/PackageManagerSettings.asset b/ProjectSettings/PackageManagerSettings.asset index f0e285b..5907600 100644 --- a/ProjectSettings/PackageManagerSettings.asset +++ b/ProjectSettings/PackageManagerSettings.asset @@ -30,18 +30,6 @@ MonoBehaviour: m_Compliance: m_Status: 0 m_Violations: [] - - m_Id: scoped:project:package.openupm.com - m_Name: package.openupm.com - m_Url: https://package.openupm.com - m_Scopes: - - com.gilzoide.update-manager - - jp.hadashikick.vcontainer - m_IsDefault: 0 - m_Capabilities: 0 - m_ConfigSource: 4 - m_Compliance: - m_Status: 0 - m_Violations: [] - m_Id: scoped:project:npm m_Name: npm m_Url: https://registry.npmjs.org @@ -53,7 +41,20 @@ MonoBehaviour: m_Compliance: m_Status: 0 m_Violations: [] - m_UserSelectedRegistryName: npm + - m_Id: scoped:project:package.openupm.com + m_Name: package.openupm.com + m_Url: https://package.openupm.com + m_Scopes: + - com.gilzoide.update-manager + - com.yasirkula.nativegallery + - jp.hadashikick.vcontainer + m_IsDefault: 0 + m_Capabilities: 0 + m_ConfigSource: 4 + m_Compliance: + m_Status: 0 + m_Violations: [] + m_UserSelectedRegistryName: package.openupm.com m_UserAddingNewScopedRegistry: 0 m_RegistryInfoDraft: m_Modified: 0 diff --git a/Readme.md b/Readme.md index 6a43be2..2a58491 100644 --- a/Readme.md +++ b/Readme.md @@ -466,7 +466,7 @@ public readonly struct ArtworkSavedSignal { ### `Capture` - Bound to the "Capture" button. -- Calls `ICaptureService.CaptureAsync(artCamera, template.PaperBackground)` → PNG bytes. +- Calls `ICaptureService.CaptureAsync()` → PNG bytes. Capture reads `IPaperRig.Surface` directly; no camera or paper-bg args needed. - Hands bytes to `IGalleryService.SaveAsync(...)`. - Fires `ArtworkCapturedSignal` then `ArtworkSavedSignal`. - Shows a quick "saved!" toast with a thumbnail of the new entry. @@ -572,20 +572,23 @@ 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. + ``` [Capture button or Next button] │ ▼ -ICaptureService.CaptureAsync(artCamera, paperBg) +ICaptureService.CaptureAsync() │ - ├─ Allocate RenderTexture (2048×2048, ARGB32) - ├─ artCamera.targetTexture = rt - ├─ Force render (artCamera.Render()) - ├─ ReadPixels into Texture2D - ├─ Composite paperBg underneath (single shader pass or CPU blend) - ├─ Encode PNG (Texture2D.EncodeToPNG) - ├─ Release RT + temp texture - └─ return byte[] + ├─ rt = _paperRig.Surface (already populated each frame) + ├─ prev = RenderTexture.active + ├─ RenderTexture.active = rt + ├─ tex = new Texture2D(rt.width, rt.height, RGBA32, false) + ├─ tex.ReadPixels(full rect, 0, 0); tex.Apply() + ├─ RenderTexture.active = prev + ├─ bytes = tex.EncodeToPNG() (on worker via UniTask.RunOnThreadPool) + ├─ Object.Destroy(tex) + └─ return bytes ▼ IGalleryService.SaveAsync(bytes, templateId) │ @@ -599,8 +602,10 @@ EventBus.Publish(new ArtworkSavedSignal(dto)) Notes: -- HUD never appears in capture because `ArtCamera` only renders the `Artwork` layer. -- Paper background can either be already present in the scene (cheap) or composited at capture time (lets the same drawing be saved with different papers). +- 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. --- @@ -726,6 +731,7 @@ Every folder under `Code/` is its own `.asmdef`. References follow the layer rul | `Darkmatter.Services.Capture` | `Services/Capture/` | `Darkmatter.Core` | | `Darkmatter.Features.MainMenu` | `Features/MainMenu/` | `Darkmatter.Core`, Libs | | `Darkmatter.Features.DrawingCatalog` | `Features/DrawingCatalog/` | `Darkmatter.Core`, Libs | +| `Darkmatter.Features.Paper` | `Features/Paper/` | `Darkmatter.Core`, `Lib.Installers` | | `Darkmatter.Features.ShapeBuilder` | `Features/ShapeBuilder/` | `Darkmatter.Core`, Libs | | `Darkmatter.Features.Coloring` | `Features/Coloring/` | `Darkmatter.Core`, `Lib.CommandStack` | | `Darkmatter.Features.History` | `Features/History/` | `Darkmatter.Core`, `Lib.CommandStack` | @@ -1101,11 +1107,13 @@ Toddler-mode error UI: | Class | Layer | Asmdef | |---|---|---| | `IDrawingTemplate`, `ShapePieceDTO`, `ColorRegionDTO` | Core | `Darkmatter.Core` | +| `IPaperRig`, `IArtInputBridge` | Core | `Darkmatter.Core` | | `ICommand`, `IUndoStack` | Core | `Darkmatter.Core` | | `BoundedUndoStack` | Libs | `Darkmatter.Lib.CommandStack` | | `AddressableAssetProviderService` | Services | `Darkmatter.Services.Assets` | | `FileGalleryService` | Services | `Darkmatter.Services.Gallery` | | `RenderTextureCaptureService` | Services | `Darkmatter.Services.Capture` | +| `PaperRig`, `ArtInputBridge`, `PaperRigModule` | Features | `Darkmatter.Features.Paper` | | `ColoringController`, `PaintRegionCommand` | Features | `Darkmatter.Features.Coloring` | | `ShapeBuilderController`, `ShapePieceView` | Features | `Darkmatter.Features.ShapeBuilder` | | `HistoryController` | Features | `Darkmatter.Features.History` | @@ -1185,17 +1193,34 @@ public interface IGalleryService { ``` #### `ICaptureService` *(Core/Capture)* -Snapshots the artwork camera to a PNG blob. +Snapshots the paper RT to a PNG blob. No arguments — dimensions and content come from `IPaperRig.Surface`. ```csharp public interface ICaptureService { - UniTask CaptureAsync( - Camera artCamera, - Sprite paperBackground, - int width = 2048, - int height = 2048); + UniTask CaptureAsync(); } ``` +#### `IPaperRig` *(Core/Paper)* +Shared art rig. The single source of truth for everything that lives in the drawing world. +```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 +} +``` + +#### `IArtInputBridge` *(Core/Paper)* +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. + #### `IProgressionService` *(Core/Progression)* Tracks which templates the child has completed and what they last opened. ```csharp @@ -1412,6 +1437,77 @@ public sealed class ShapePieceFactory { --- +### 32.5b Feature — `Paper` + +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. + +#### `PaperRig : MonoBehaviour, IPaperRig` *(Rig)* +Scene-bound component placed on a GameObject in `ColorBook.unity`. Owns the RT lifecycle. +```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) + +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); +} +``` +- **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. + +#### `ArtInputBridge : MonoBehaviour, IArtInputBridge` *(Input)* +Lives on the same UI Canvas as the paper `RawImage`. +```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 + +public void Register(IContainerBuilder builder) { + builder.RegisterInstance(_rig); + builder.RegisterInstance(_bridge); +} +``` +- Registers as `Instance` because both are MonoBehaviours already in the scene. +- Lifetime is implicitly tied to the scene (Unity destroys them on unload). + +--- + ### 32.6 Feature — `Coloring` #### `ColoringStateRepository` *(Repository)* @@ -1453,7 +1549,15 @@ public sealed class ColorRegionView : MonoBehaviour { ``` #### `ColoringInputBinder` *(Systems)* — `IStartable, IDisposable` -Subscribes to `IInputReader.PointerDown`, raycasts on the `Artwork` layer mask, calls `ColoringController.PaintRegion(view)` on hit. +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. #### `PaintRegionCommand` *(Commands)* Source in section 23. Holds `view`, `fromColor`, `toColor`, `bus`. Symmetrical execute/undo. @@ -1503,14 +1607,16 @@ Wires controller `StateChanged` ↔ view enable/disable; view click events → c #### `CaptureController` *(Systems)* The orchestrator behind the "Capture" button. Stateless other than guarding against concurrent captures. ```csharp -// fields: ICaptureService _capture, IGalleryService _gallery, IEventBus _bus, ColorBookSceneRefs _refs +// fields: ICaptureService _capture, IGalleryService _gallery, IEventBus _bus public sealed class CaptureController { public bool IsCapturing { get; } - public UniTask CaptureCurrentAsync(string templateId, Sprite paperBg); + public UniTask CaptureCurrentAsync(string templateId); } // pub: ArtworkCapturedSignal (mid-flow), ArtworkSavedSignal (post-save) ``` +- **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. #### `CaptureButtonPresenter` *(UI)* Wires button click → `CaptureController.CaptureCurrentAsync`. Disables button while in progress. Shows toast on `ArtworkSavedSignal`. @@ -1641,8 +1747,11 @@ Implemented as `ScriptableObject` per feature so scopes can drag them in the ins | `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 | | `HistoryController` | Feature | Undo/redo facade | undo stack, bus | -| `CaptureController` | Feature | Capture+save orchestration | capture svc, gallery, bus, refs | +| `CaptureController` | Feature | Capture+save orchestration | capture svc, gallery, bus | | `ColorBookFlowController` | Feature | Scene FSM | bus, catalog, builder, coloring, capture, progression | | `GalleryPresenter` | Feature | Art book listing | gallery, share, view, bus | | `BoundedUndoStack` | Lib | Capped undo store | — | @@ -1650,7 +1759,7 @@ Implemented as `ScriptableObject` per feature so scopes can drag them in the ins | `Fsm` | Lib | Generic FSM | — | | `AddressableAssetProviderService` | Service | Addressables wrapper | — | | `FileGalleryService` | Service | Gallery file IO | paths, thumb gen, bus | -| `RenderTextureCaptureService` | Service | PNG render | — | +| `RenderTextureCaptureService` | Service | PNG render from rig.Surface | paper rig | | `JsonPersistenceService` | Service | Settings/progression IO | — | | `SceneService` | Service | Async scene loads | — | | `AudioService` | Service | SFX playback | assets |