Compare commits

..

7 Commits

Author SHA1 Message Date
Savya Bikram Shah
b77976039a built 2026-07-09 15:17:54 +05:45
Savya Bikram Shah
f6f5474366 Merge branch 'work_branch' of https://git.darkmatterproduction.org/savya/Colorbook into savya
# Conflicts:
#	Assets/Darkmatter/Content/Fonts/static/Fredoka-Bold SDF Blue Outline.asset
2026-07-09 10:37:28 +05:45
Savya Bikram Shah
3abf19b5bb minor 2026-07-09 10:34:43 +05:45
Mausham
f6d672995e added sound in color completion 2026-07-08 18:36:36 +05:45
Savya Bikram Shah
35e4bf8067 Merge branch 'work_branch' of https://git.darkmatterproduction.org/savya/Colorbook into savya
# Conflicts:
#	Assets/Darkmatter/Content/Fonts/static/Fredoka-Bold SDF Blue Outline.asset
2026-07-07 15:53:34 +05:45
Savya Bikram Shah
df43824fb1 Back button bug fix 2026-07-07 15:53:13 +05:45
Savya Bikram Shah
e167af11fd Fixes optimization and UI/UX updates 2026-07-07 13:05:48 +05:45
204 changed files with 2025 additions and 2196 deletions

BIN
.DS_Store vendored

Binary file not shown.

BIN
Assets/.DS_Store vendored

Binary file not shown.

Binary file not shown.

View File

@@ -14,6 +14,7 @@ namespace Darkmatter.Core.Contracts.Features.DrawingCatalog
GameObject DrawingPrefab { get; } GameObject DrawingPrefab { get; }
GameObject ColoringPrefab { get; } GameObject ColoringPrefab { get; }
GameObject CompletionAnimationPrefab { get; } GameObject CompletionAnimationPrefab { get; }
AudioClip CompletionSound { get; }
IReadOnlyList<ShapeSO> Pieces { get; } IReadOnlyList<ShapeSO> Pieces { get; }
IReadOnlyList<ColorRegionDTO> Regions { get; } IReadOnlyList<ColorRegionDTO> Regions { get; }
string ColorPaletteId { get; } string ColorPaletteId { get; }

View File

@@ -8,7 +8,10 @@ namespace Darkmatter.Core.Contracts.Features.DrawingCatalog
{ {
UniTask FetchAsync(); UniTask FetchAsync();
IReadOnlyList<string> AllTemplateIds { get; } IReadOnlyList<string> AllTemplateIds { get; }
UniTask<Sprite> GetThumbnailAsync(string id); // owned = true when the sprite (and its texture) were created for this call from a
// saved thumbnail on disk — the caller must Destroy() both when done. owned = false
// means the template's default thumbnail asset, which must NOT be destroyed.
UniTask<(Sprite sprite, bool owned)> GetThumbnailAsync(string id);
UniTask<IDrawingTemplate> LoadAsync(string id); UniTask<IDrawingTemplate> LoadAsync(string id);
void ReleaseAll(); void ReleaseAll();
string GetNextTemplate(string currentId); string GetNextTemplate(string currentId);

View File

@@ -18,6 +18,7 @@ namespace Darkmatter.Core.Data.Static.Features.DrawingTemplate
[SerializeField] private GameObject drawingPrefab; [SerializeField] private GameObject drawingPrefab;
[SerializeField] private GameObject coloringPrefab; [SerializeField] private GameObject coloringPrefab;
[SerializeField] private GameObject completionAnimationPrefab; [SerializeField] private GameObject completionAnimationPrefab;
[SerializeField] private AudioClip completionSound;
[SerializeField] private List<ShapeSO> pieces = new(); [SerializeField] private List<ShapeSO> pieces = new();
[SerializeField] private List<RegionAuthoring> regions = new(); [SerializeField] private List<RegionAuthoring> regions = new();
[SerializeField] private string colorPaletteId = "defaultPalette"; [SerializeField] private string colorPaletteId = "defaultPalette";
@@ -33,6 +34,7 @@ namespace Darkmatter.Core.Data.Static.Features.DrawingTemplate
public GameObject DrawingPrefab => drawingPrefab; public GameObject DrawingPrefab => drawingPrefab;
public GameObject ColoringPrefab => coloringPrefab; public GameObject ColoringPrefab => coloringPrefab;
public GameObject CompletionAnimationPrefab => completionAnimationPrefab; public GameObject CompletionAnimationPrefab => completionAnimationPrefab;
public AudioClip CompletionSound => completionSound;
public IReadOnlyList<ShapeSO> Pieces => pieces; public IReadOnlyList<ShapeSO> Pieces => pieces;
public IReadOnlyList<ColorRegionDTO> Regions => Array.Empty<ColorRegionDTO>(); public IReadOnlyList<ColorRegionDTO> Regions => Array.Empty<ColorRegionDTO>();
public string ColorPaletteId => colorPaletteId; public string ColorPaletteId => colorPaletteId;
@@ -41,7 +43,7 @@ namespace Darkmatter.Core.Data.Static.Features.DrawingTemplate
#if UNITY_EDITOR #if UNITY_EDITOR
public void EditorSet(string newId, string newDisplayName, Sprite thumbnail, public void EditorSet(string newId, string newDisplayName, Sprite thumbnail,
GameObject drawing, GameObject coloring, GameObject completionAnimation, GameObject drawing, GameObject coloring, GameObject completionAnimation, AudioClip completionSoundClip,
List<ShapeSO> newPieces, List<ShapeSO> newPieces,
List<RegionAuthoring> newRegions, string paletteId) List<RegionAuthoring> newRegions, string paletteId)
{ {
@@ -51,6 +53,7 @@ namespace Darkmatter.Core.Data.Static.Features.DrawingTemplate
drawingPrefab = drawing; drawingPrefab = drawing;
coloringPrefab = coloring; coloringPrefab = coloring;
completionAnimationPrefab = completionAnimation; completionAnimationPrefab = completionAnimation;
completionSound = completionSoundClip;
pieces = newPieces ?? new List<ShapeSO>(); pieces = newPieces ?? new List<ShapeSO>();
regions = newRegions ?? new List<RegionAuthoring>(); regions = newRegions ?? new List<RegionAuthoring>();
colorPaletteId = paletteId; colorPaletteId = paletteId;

View File

@@ -6,7 +6,8 @@
"GUID:c1c03c0e5b2f4412b9f2be1c20d6a9b1", "GUID:c1c03c0e5b2f4412b9f2be1c20d6a9b1",
"GUID:b4c9f7fbf1e144933a1797dc208ece5f", "GUID:b4c9f7fbf1e144933a1797dc208ece5f",
"GUID:b0214a6008ed146ff8f122a6a9c2f6cc", "GUID:b0214a6008ed146ff8f122a6a9c2f6cc",
"GUID:f51ebe6a0ceec4240a699833d6309b23" "GUID:f51ebe6a0ceec4240a699833d6309b23",
"GUID:75469ad4d38634e559750d17036d5f7c"
], ],
"includePlatforms": [], "includePlatforms": [],
"excludePlatforms": [], "excludePlatforms": [],

View File

@@ -0,0 +1,48 @@
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.Rendering;
using VContainer.Unity;
namespace Darkmatter.Features.AppBoot.Flow
{
// A coloring page is static most of the time; re-rendering it at a locked 60 fps only
// turns battery into heat. Keep the player loop at 60 Hz (input still sampled every
// frame, so the first touch never lands on a skipped frame) but render every other
// frame while nothing has been touched for a few seconds.
public sealed class AdaptiveFrameRate : ITickable
{
private const float FullRateHoldSeconds = 3f;
private const int IdleRenderInterval = 2; // 60 Hz loop -> 30 fps rendering when idle
private float _lastInteractionTime;
public AdaptiveFrameRate()
{
// Treat boot as an interaction so the intro video starts at full rate.
_lastInteractionTime = Time.unscaledTime;
}
public void Tick()
{
if (IsPointerPressed())
_lastInteractionTime = Time.unscaledTime;
var desired = Time.unscaledTime - _lastInteractionTime < FullRateHoldSeconds
? 1
: IdleRenderInterval;
if (OnDemandRendering.renderFrameInterval != desired)
OnDemandRendering.renderFrameInterval = desired;
}
private static bool IsPointerPressed()
{
var touchscreen = Touchscreen.current;
if (touchscreen != null && touchscreen.primaryTouch.press.isPressed)
return true;
var mouse = Mouse.current;
return mouse != null && mouse.leftButton.isPressed;
}
}
}

View File

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

View File

@@ -16,6 +16,7 @@ namespace Darkmatter.Features.AppBoot.Installers
if (sceneRefs != null) if (sceneRefs != null)
builder.RegisterComponent(sceneRefs); builder.RegisterComponent(sceneRefs);
builder.RegisterEntryPoint<AppBootFlow>(); builder.RegisterEntryPoint<AppBootFlow>();
builder.RegisterEntryPoint<AdaptiveFrameRate>();
} }
} }
} }

View File

@@ -7,8 +7,11 @@ using Darkmatter.Core.Contracts.Features.DrawingCatalog;
using Darkmatter.Core.Contracts.Features.GameplayFlow; using Darkmatter.Core.Contracts.Features.GameplayFlow;
using Darkmatter.Core.Contracts.Features.History; using Darkmatter.Core.Contracts.Features.History;
using Darkmatter.Core.Contracts.Services.Assets; using Darkmatter.Core.Contracts.Services.Assets;
using Darkmatter.Core.Contracts.Services.Audio;
using Darkmatter.Core.Data.Dynamic.Services.Audio;
using Darkmatter.Core.Data.Signals.Features.Coloring; using Darkmatter.Core.Data.Signals.Features.Coloring;
using Darkmatter.Core.Data.Static.Features.Coloring; using Darkmatter.Core.Data.Static.Features.Coloring;
using Darkmatter.Core.Enums.Services.Audio;
using Darkmatter.Features.Coloring.Commands; using Darkmatter.Features.Coloring.Commands;
using Darkmatter.Features.Coloring.UI; using Darkmatter.Features.Coloring.UI;
using Darkmatter.Libs.Observer; using Darkmatter.Libs.Observer;
@@ -28,11 +31,14 @@ public class ColoringController : IColoringController, IDisposable
private readonly IUndoStack _history; private readonly IUndoStack _history;
private readonly IGameplaySceneRefs _refs; private readonly IGameplaySceneRefs _refs;
private readonly ColorPaletteHolderView _paletteHolder; private readonly ColorPaletteHolderView _paletteHolder;
private readonly IAudioService _audioService;
private GameObject _colorInstance; private GameObject _colorInstance;
private GameObject _colorButtonPrefab; private GameObject _colorButtonPrefab;
private GameObject _completionAnimationInstance; private GameObject _completionAnimationInstance;
private CompletionAnimationView _completionAnimationView; private CompletionAnimationView _completionAnimationView;
private AudioClip _completionSound;
private AudioHandle _completionSoundHandle = AudioHandle.Invalid;
private readonly ColoringPinchZoomController _pinchZoom; private readonly ColoringPinchZoomController _pinchZoom;
private bool _isPlayingCompletionAnimation; private bool _isPlayingCompletionAnimation;
private readonly List<ColorRegionView> _regions = new(); private readonly List<ColorRegionView> _regions = new();
@@ -47,7 +53,8 @@ public class ColoringController : IColoringController, IDisposable
IAssetProviderService assetProviderService, IAssetProviderService assetProviderService,
IUndoStack history, IUndoStack history,
IGameplaySceneRefs refs, IGameplaySceneRefs refs,
ColorPaletteHolderView paletteHolder) ColorPaletteHolderView paletteHolder,
IAudioService audioService)
{ {
_repository = repository; _repository = repository;
_buttonFactory = buttonFactory; _buttonFactory = buttonFactory;
@@ -57,6 +64,7 @@ public class ColoringController : IColoringController, IDisposable
_pinchZoom = pinchZoom; _pinchZoom = pinchZoom;
_refs = refs; _refs = refs;
_paletteHolder = paletteHolder; _paletteHolder = paletteHolder;
_audioService = audioService;
} }
public async UniTask InitializeRegionsAsync(IDrawingTemplate template, public async UniTask InitializeRegionsAsync(IDrawingTemplate template,
@@ -117,6 +125,7 @@ public class ColoringController : IColoringController, IDisposable
_pinchZoom.ResetZoom(); _pinchZoom.ResetZoom();
_completionAnimationInstance.SetActive(true); _completionAnimationInstance.SetActive(true);
_isPlayingCompletionAnimation = true; _isPlayingCompletionAnimation = true;
PlayCompletionSound();
try try
{ {
await _completionAnimationView.PlayAsync(ct); await _completionAnimationView.PlayAsync(ct);
@@ -129,6 +138,23 @@ public class ColoringController : IColoringController, IDisposable
} }
} }
// Starts the template's completion sound together with the completion animation.
// A OneShot is left to finish naturally on success; StopCompletionSound() (called from
// Clear) cuts it off if the controller is torn down mid-play.
private void PlayCompletionSound()
{
if (_audioService == null || _completionSound == null) return;
_completionSoundHandle = _audioService.Play(
new AudioRequest(_completionSound, AudioChannel.Sfx, AudioPlayMode.OneShot));
}
private void StopCompletionSound()
{
if (_audioService != null && _completionSoundHandle.IsValid)
_audioService.Stop(_completionSoundHandle);
_completionSoundHandle = AudioHandle.Invalid;
}
public bool HasNonAuthoredColors public bool HasNonAuthoredColors
{ {
get get
@@ -167,6 +193,7 @@ public class ColoringController : IColoringController, IDisposable
{ {
_history.Drop(); _history.Drop();
_isPlayingCompletionAnimation = false; _isPlayingCompletionAnimation = false;
StopCompletionSound();
foreach (var button in _buttons) foreach (var button in _buttons)
if (button != null) if (button != null)
@@ -182,6 +209,7 @@ public class ColoringController : IColoringController, IDisposable
_completionAnimationInstance = null; _completionAnimationInstance = null;
_completionAnimationView = null; _completionAnimationView = null;
} }
_completionSound = null;
if (_colorInstance != null) if (_colorInstance != null)
{ {
@@ -205,6 +233,7 @@ public class ColoringController : IColoringController, IDisposable
_completionAnimationView = _completionAnimationInstance.GetComponentInChildren<CompletionAnimationView>(includeInactive: true); _completionAnimationView = _completionAnimationInstance.GetComponentInChildren<CompletionAnimationView>(includeInactive: true);
_completionAnimationInstance.SetActive(false); _completionAnimationInstance.SetActive(false);
} }
_completionSound = template.CompletionSound;
var views = _colorInstance.GetComponentsInChildren<ColorRegionView>(includeInactive: true); var views = _colorInstance.GetComponentsInChildren<ColorRegionView>(includeInactive: true);
foreach (var region in views) foreach (var region in views)

View File

@@ -1,5 +1,6 @@
using System; using System;
using UnityEngine; using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem; using UnityEngine.InputSystem;
namespace Darkmatter.Features.Coloring.Systems namespace Darkmatter.Features.Coloring.Systems
@@ -8,6 +9,7 @@ namespace Darkmatter.Features.Coloring.Systems
public sealed class ColoringPinchZoomController : MonoBehaviour public sealed class ColoringPinchZoomController : MonoBehaviour
{ {
private const float MinimumPinchDistance = 8f; private const float MinimumPinchDistance = 8f;
private const float PanZoomEpsilon = 0.0001f;
[SerializeField] private float minZoom = 1f; [SerializeField] private float minZoom = 1f;
[SerializeField] private float maxZoom = 3f; [SerializeField] private float maxZoom = 3f;
@@ -22,6 +24,7 @@ namespace Darkmatter.Features.Coloring.Systems
private float _currentZoom = 1f; private float _currentZoom = 1f;
private Vector3 _baseScale = Vector3.one; private Vector3 _baseScale = Vector3.one;
private Vector2 _baseAnchoredPosition; private Vector2 _baseAnchoredPosition;
private PanDragRelay _panRelay;
public void SetTarget(RectTransform target) public void SetTarget(RectTransform target)
{ {
@@ -34,6 +37,26 @@ namespace Darkmatter.Features.Coloring.Systems
_baseAnchoredPosition = target != null ? target.anchoredPosition : Vector2.zero; _baseAnchoredPosition = target != null ? target.anchoredPosition : Vector2.zero;
_currentZoom = 1f; _currentZoom = 1f;
enabled = _target != null && _parent != null; enabled = _target != null && _parent != null;
_panRelay = null;
if (_target != null)
{
_panRelay = _target.gameObject.GetComponent<PanDragRelay>();
if (_panRelay == null)
_panRelay = _target.gameObject.AddComponent<PanDragRelay>();
_panRelay.Controller = this;
}
UpdatePanRelayState();
}
// While not zoomed the relay is disabled so the event system never resolves it as a
// drag target — sloppy kid taps keep registering as clicks (paints) exactly as before.
// Zoomed in, drags become pans and are suppressed as clicks by uGUI.
private void UpdatePanRelayState()
{
if (_panRelay != null)
_panRelay.enabled = _currentZoom > 1f + PanZoomEpsilon;
} }
public void ResetZoom() public void ResetZoom()
@@ -46,6 +69,7 @@ namespace Darkmatter.Features.Coloring.Systems
_currentZoom = 1f; _currentZoom = 1f;
_isPinching = false; _isPinching = false;
UpdatePanRelayState();
} }
public IDisposable UseNonZoomedView() public IDisposable UseNonZoomedView()
@@ -78,6 +102,7 @@ namespace Darkmatter.Features.Coloring.Systems
_isPinching = false; _isPinching = false;
TryApplyScrollZoom(); TryApplyScrollZoom();
TryApplyMousePan();
} }
private bool TryApplyPinchZoom() private bool TryApplyPinchZoom()
@@ -111,8 +136,16 @@ namespace Darkmatter.Features.Coloring.Systems
return true; return true;
} }
// Anchor the content point that was under the previous midpoint to the current
// midpoint: distance change zooms, midpoint movement pans — one gesture does both.
Vector2 previousMidpoint = (firstTouch.PreviousPosition + secondTouch.PreviousPosition) * 0.5f;
if (!TryGetParentLocalPoint(previousMidpoint, out Vector2 previousMidpointLocal))
{
return true;
}
float targetZoom = Mathf.Clamp(_currentZoom * (distance / previousDistance), minZoom, maxZoom); float targetZoom = Mathf.Clamp(_currentZoom * (distance / previousDistance), minZoom, maxZoom);
ZoomTowards(midpointLocal, targetZoom); ApplyZoomPan(previousMidpointLocal, midpointLocal, targetZoom);
return true; return true;
} }
@@ -152,12 +185,123 @@ namespace Darkmatter.Features.Coloring.Systems
return; return;
} }
Vector2 contentPoint = (parentLocalPoint - _target.anchoredPosition) / _currentZoom; ApplyZoomPan(parentLocalPoint, parentLocalPoint, targetZoom);
}
private void ApplyZoomPan(Vector2 previousLocalPoint, Vector2 currentLocalPoint, float targetZoom)
{
Vector2 contentPoint = (previousLocalPoint - _target.anchoredPosition) / _currentZoom;
_currentZoom = targetZoom; _currentZoom = targetZoom;
_target.localScale = _baseScale * _currentZoom; _target.localScale = _baseScale * _currentZoom;
_target.anchoredPosition = Mathf.Approximately(_currentZoom, minZoom) _target.anchoredPosition = ClampPan(currentLocalPoint - contentPoint * _currentZoom);
? _baseAnchoredPosition UpdatePanRelayState();
: parentLocalPoint - contentPoint * _currentZoom; }
// Editor/desktop convenience: drag with the right or middle mouse button to pan while zoomed.
private void TryApplyMousePan()
{
Mouse mouse = Mouse.current;
if (mouse == null || (!mouse.rightButton.isPressed && !mouse.middleButton.isPressed))
{
return;
}
if (_currentZoom <= 1f + PanZoomEpsilon)
{
return;
}
Vector2 delta = mouse.delta.ReadValue();
if (delta == Vector2.zero)
{
return;
}
Vector2 screenPosition = mouse.position.ReadValue();
if (!IsInputPointAllowed(screenPosition))
{
return;
}
if (!TryGetParentLocalPoint(screenPosition, out Vector2 currentLocal) ||
!TryGetParentLocalPoint(screenPosition - delta, out Vector2 previousLocal))
{
return;
}
_target.anchoredPosition = ClampPan(_target.anchoredPosition + (currentLocal - previousLocal));
}
// Single-finger (or left-mouse) drag pan, fed by PanDragRelay on the paper root.
// Going through uGUI's drag pipeline matters: once the drag threshold is crossed the
// event system cancels the pending click, so a pan can never paint the region the
// finger ends on — a plain tap still paints.
private void OnPanDrag(PointerEventData eventData, Transform source)
{
if (_target == null || source != _target.transform)
{
return;
}
if (_isPinching || HasMultiTouch())
{
return; // two-finger gesture owns the view
}
if (_currentZoom <= 1f + PanZoomEpsilon)
{
return;
}
if (!TryGetParentLocalPoint(eventData.position, out Vector2 currentLocal) ||
!TryGetParentLocalPoint(eventData.position - eventData.delta, out Vector2 previousLocal))
{
return;
}
_target.anchoredPosition = ClampPan(_target.anchoredPosition + (currentLocal - previousLocal));
}
private static bool HasMultiTouch()
{
Touchscreen touchscreen = Touchscreen.current;
if (touchscreen == null)
{
return false;
}
int count = 0;
foreach (var touchControl in touchscreen.touches)
{
if (touchControl.press.isPressed && ++count > 1)
{
return true;
}
}
return false;
}
// The scaled paper must keep covering its zoom-1 footprint: panning can never expose
// background inside the area the drawing occupies when not zoomed.
private Vector2 ClampPan(Vector2 anchoredPosition)
{
if (_currentZoom <= 1f + PanZoomEpsilon)
{
return _baseAnchoredPosition;
}
Rect rect = _target.rect;
float growth = 1f - _currentZoom; // negative while zoomed in
float minX = _baseAnchoredPosition.x + rect.xMax * _baseScale.x * growth;
float maxX = _baseAnchoredPosition.x + rect.xMin * _baseScale.x * growth;
float minY = _baseAnchoredPosition.y + rect.yMax * _baseScale.y * growth;
float maxY = _baseAnchoredPosition.y + rect.yMin * _baseScale.y * growth;
return new Vector2(
Mathf.Clamp(anchoredPosition.x, minX, maxX),
Mathf.Clamp(anchoredPosition.y, minY, maxY));
} }
private bool TryGetParentLocalPoint(Vector2 screenPoint, out Vector2 localPoint) private bool TryGetParentLocalPoint(Vector2 screenPoint, out Vector2 localPoint)
@@ -233,6 +377,7 @@ namespace Darkmatter.Features.Coloring.Systems
_target.anchoredPosition = state.AnchoredPosition; _target.anchoredPosition = state.AnchoredPosition;
_currentZoom = state.CurrentZoom; _currentZoom = state.CurrentZoom;
_isPinching = false; _isPinching = false;
UpdatePanRelayState();
} }
private readonly struct ViewState private readonly struct ViewState
@@ -285,5 +430,25 @@ namespace Darkmatter.Features.Coloring.Systems
Delta = delta; Delta = delta;
} }
} }
// Runtime-attached to the paper root (SetTarget); regions are its children, so drags
// that start on a region bubble up here while taps stay clicks. Implementing the uGUI
// drag interfaces is what makes the event system suppress the region click on drag.
private sealed class PanDragRelay : MonoBehaviour, IBeginDragHandler, IDragHandler
{
internal ColoringPinchZoomController Controller;
public void OnBeginDrag(PointerEventData eventData)
{
}
public void OnDrag(PointerEventData eventData)
{
if (Controller != null)
{
Controller.OnPanDrag(eventData, transform);
}
}
}
} }
} }

View File

@@ -25,6 +25,12 @@ namespace Darkmatter.Features.DrawingCatalog
private readonly CancellationTokenSource _cts = new(); private readonly CancellationTokenSource _cts = new();
private IDisposable _openSubscription; private IDisposable _openSubscription;
// Sprites/textures decoded from saved thumbnails are created fresh on every refresh
// and are owned by this presenter — without destroying the previous batch each
// Colorbook visit leaks several MB per painted drawing until the OS kills the app.
private readonly List<Sprite> _ownedSprites = new();
private readonly List<Texture2D> _ownedTextures = new();
public DrawingCatalogPresenter(DrawingCatalogView view, IDrawingCatalogController controller, public DrawingCatalogPresenter(DrawingCatalogView view, IDrawingCatalogController controller,
IDrawingTemplateCatalog catalog, IEventBus eventBus) IDrawingTemplateCatalog catalog, IEventBus eventBus)
{ {
@@ -108,6 +114,12 @@ namespace Darkmatter.Features.DrawingCatalog
RefreshAsync(_cts.Token).Forget(); RefreshAsync(_cts.Token).Forget();
private async UniTask RefreshAsync(CancellationToken ct) private async UniTask RefreshAsync(CancellationToken ct)
{
var newSprites = new List<Sprite>();
var newTextures = new List<Texture2D>();
var applied = false;
try
{ {
var ids = ToFixedRowGridOrder(_controller.VisibleIds); var ids = ToFixedRowGridOrder(_controller.VisibleIds);
var vms = new List<CatalogItemVM>(ids.Count); var vms = new List<CatalogItemVM>(ids.Count);
@@ -118,7 +130,13 @@ namespace Darkmatter.Features.DrawingCatalog
foreach (var id in ids) foreach (var id in ids)
{ {
if (ct.IsCancellationRequested) return; if (ct.IsCancellationRequested) return;
var thumb = await _catalog.GetThumbnailAsync(id); var (thumb, owned) = await _catalog.GetThumbnailAsync(id);
if (owned)
{
newSprites.Add(thumb);
newTextures.Add(thumb.texture);
}
vms.Add(new CatalogItemVM(id, thumb)); vms.Add(new CatalogItemVM(id, thumb));
} }
@@ -127,12 +145,44 @@ namespace Darkmatter.Features.DrawingCatalog
_view.SetItems(vms); _view.SetItems(vms);
_view.SetPagination(_currentPage, _totalPages); _view.SetPagination(_currentPage, _totalPages);
// Buttons now reference the new batch; the previous one can go.
DestroyOwnedThumbnails();
_ownedSprites.AddRange(newSprites);
_ownedTextures.AddRange(newTextures);
applied = true;
// Unblock InitializeAsync: items are now on screen, so the loading screen can hide. // Unblock InitializeAsync: items are now on screen, so the loading screen can hide.
_controller.NotifyPopulated(); _controller.NotifyPopulated();
// Cue the first-run tutorial that the catalog grid is on screen and tappable. // Cue the first-run tutorial that the catalog grid is on screen and tappable.
_eventBus.Publish(new DrawingCatalogReadySignal()); _eventBus.Publish(new DrawingCatalogReadySignal());
} }
catch (Exception e)
{
Debug.LogError($"[DrawingCatalog] Failed to refresh catalog: {e}");
}
finally
{
// Aborted refresh (cancelled or threw): don't strand the freshly decoded batch.
if (!applied)
{
foreach (var s in newSprites) UnityEngine.Object.Destroy(s);
foreach (var t in newTextures) UnityEngine.Object.Destroy(t);
}
// Idempotent — always unblock the flow; a stuck loading screen is worse than
// an empty catalog.
_controller.NotifyPopulated();
}
}
private void DestroyOwnedThumbnails()
{
foreach (var s in _ownedSprites) UnityEngine.Object.Destroy(s);
foreach (var t in _ownedTextures) UnityEngine.Object.Destroy(t);
_ownedSprites.Clear();
_ownedTextures.Clear();
}
private static List<string> ToFixedRowGridOrder(IReadOnlyList<string> ids) private static List<string> ToFixedRowGridOrder(IReadOnlyList<string> ids)
{ {
@@ -160,6 +210,7 @@ namespace Darkmatter.Features.DrawingCatalog
_openSubscription?.Dispose(); _openSubscription?.Dispose();
_cts.Cancel(); _cts.Cancel();
_cts.Dispose(); _cts.Dispose();
DestroyOwnedThumbnails();
} }
} }
} }

View File

@@ -217,6 +217,11 @@ namespace Darkmatter.Features.DrawingTemplates.Editor
: $"{t.Difficulty} (auto: {t.Pieces.Count} pieces + {t.AuthoredRegions.Count} regions)")); : $"{t.Difficulty} (auto: {t.Pieces.Count} pieces + {t.AuthoredRegions.Count} regions)"));
EditorGUILayout.PropertyField(so.FindProperty("defaultThumbnail")); EditorGUILayout.PropertyField(so.FindProperty("defaultThumbnail"));
EditorGUILayout.Space(6);
EditorGUILayout.LabelField("Sound", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(so.FindProperty("completionSound"),
new GUIContent("Completion Sound (MP3)", "Audio clip (e.g. an imported .mp3) played in sync with the completion animation."));
EditorGUILayout.Space(6); EditorGUILayout.Space(6);
EditorGUILayout.LabelField("Prefabs", EditorStyles.boldLabel); EditorGUILayout.LabelField("Prefabs", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(so.FindProperty("drawingPrefab"), new GUIContent("Drawing Prefab (SlotMarkers)")); EditorGUILayout.PropertyField(so.FindProperty("drawingPrefab"), new GUIContent("Drawing Prefab (SlotMarkers)"));
@@ -241,7 +246,7 @@ namespace Darkmatter.Features.DrawingTemplates.Editor
Undo.RecordObject(t, "Clear Pieces"); Undo.RecordObject(t, "Clear Pieces");
var emptyPieces = new List<ShapeSO>(); var emptyPieces = new List<ShapeSO>();
t.EditorSet(t.Id, t.DisplayName, t.DefaultThumbnail, t.DrawingPrefab, t.ColoringPrefab, t.EditorSet(t.Id, t.DisplayName, t.DefaultThumbnail, t.DrawingPrefab, t.ColoringPrefab,
t.CompletionAnimationPrefab, emptyPieces, t.AuthoredRegions.ToList(), t.ColorPaletteId); t.CompletionAnimationPrefab, t.CompletionSound, emptyPieces, t.AuthoredRegions.ToList(), t.ColorPaletteId);
EditorUtility.SetDirty(t); EditorUtility.SetDirty(t);
so.Update(); so.Update();
} }
@@ -273,7 +278,7 @@ namespace Darkmatter.Features.DrawingTemplates.Editor
{ {
Undo.RecordObject(t, "Clear Regions"); Undo.RecordObject(t, "Clear Regions");
t.EditorSet(t.Id, t.DisplayName, t.DefaultThumbnail, t.DrawingPrefab, t.ColoringPrefab, t.EditorSet(t.Id, t.DisplayName, t.DefaultThumbnail, t.DrawingPrefab, t.ColoringPrefab,
t.CompletionAnimationPrefab, t.Pieces.ToList(), new List<RegionAuthoring>(), t.ColorPaletteId); t.CompletionAnimationPrefab, t.CompletionSound, t.Pieces.ToList(), new List<RegionAuthoring>(), t.ColorPaletteId);
EditorUtility.SetDirty(t); EditorUtility.SetDirty(t);
so.Update(); so.Update();
} }
@@ -341,7 +346,7 @@ namespace Darkmatter.Features.DrawingTemplates.Editor
} }
Undo.RecordObject(t, "Scan Drawing Prefab"); Undo.RecordObject(t, "Scan Drawing Prefab");
t.EditorSet(t.Id, t.DisplayName, t.DefaultThumbnail, t.DrawingPrefab, t.ColoringPrefab, t.EditorSet(t.Id, t.DisplayName, t.DefaultThumbnail, t.DrawingPrefab, t.ColoringPrefab,
t.CompletionAnimationPrefab, pieces, t.AuthoredRegions.ToList(), t.ColorPaletteId); t.CompletionAnimationPrefab, t.CompletionSound, pieces, t.AuthoredRegions.ToList(), t.ColorPaletteId);
EditorUtility.SetDirty(t); EditorUtility.SetDirty(t);
_lastScanReport = $"Found {markers.Length} SlotMarker(s), {pieces.Count} unique ShapeSO. {missing} marker(s) had no ShapeSO assigned."; _lastScanReport = $"Found {markers.Length} SlotMarker(s), {pieces.Count} unique ShapeSO. {missing} marker(s) had no ShapeSO assigned.";
Validate(); Validate();
@@ -421,7 +426,7 @@ namespace Darkmatter.Features.DrawingTemplates.Editor
Undo.RecordObject(t, "Scan Coloring Prefab"); Undo.RecordObject(t, "Scan Coloring Prefab");
t.EditorSet(t.Id, t.DisplayName, t.DefaultThumbnail, t.DrawingPrefab, t.ColoringPrefab, t.EditorSet(t.Id, t.DisplayName, t.DefaultThumbnail, t.DrawingPrefab, t.ColoringPrefab,
t.CompletionAnimationPrefab, t.Pieces.ToList(), regions, t.ColorPaletteId); t.CompletionAnimationPrefab, t.CompletionSound, t.Pieces.ToList(), regions, t.ColorPaletteId);
EditorUtility.SetDirty(t); EditorUtility.SetDirty(t);
var report = new System.Text.StringBuilder(); var report = new System.Text.StringBuilder();
@@ -621,7 +626,7 @@ namespace Darkmatter.Features.DrawingTemplates.Editor
if (string.IsNullOrEmpty(path)) return; if (string.IsNullOrEmpty(path)) return;
var t = CreateInstance<DrawingTemplateSO>(); var t = CreateInstance<DrawingTemplateSO>();
var name = Path.GetFileNameWithoutExtension(path); var name = Path.GetFileNameWithoutExtension(path);
t.EditorSet(name, name, null, null, null, null, new List<ShapeSO>(), new List<RegionAuthoring>(), "defaultPalette"); t.EditorSet(name, name, null, null, null, null, null, new List<ShapeSO>(), new List<RegionAuthoring>(), "defaultPalette");
AssetDatabase.CreateAsset(t, path); AssetDatabase.CreateAsset(t, path);
AssetDatabase.SaveAssets(); AssetDatabase.SaveAssets();
Refresh(); Refresh();

View File

@@ -53,7 +53,7 @@ namespace Darkmatter.Features.DrawingTemplates.Systems
_initialized = true; _initialized = true;
} }
public async UniTask<Sprite> GetThumbnailAsync(string id) public async UniTask<(Sprite sprite, bool owned)> GetThumbnailAsync(string id)
{ {
if (!_byId.TryGetValue(id, out var t)) if (!_byId.TryGetValue(id, out var t))
throw new KeyNotFoundException($"Template '{id}' not in catalog. Did InitializeAsync run?"); throw new KeyNotFoundException($"Template '{id}' not in catalog. Did InitializeAsync run?");
@@ -62,9 +62,9 @@ namespace Darkmatter.Features.DrawingTemplates.Systems
if (savedTex != null) if (savedTex != null)
{ {
var rect = new Rect(0, 0, savedTex.width, savedTex.height); var rect = new Rect(0, 0, savedTex.width, savedTex.height);
return Sprite.Create(savedTex, rect, new Vector2(0.5f, 0.5f)); return (Sprite.Create(savedTex, rect, new Vector2(0.5f, 0.5f)), true);
} }
return t.DefaultThumbnail; return (t.DefaultThumbnail, false);
} }
public UniTask<IDrawingTemplate> LoadAsync(string id) public UniTask<IDrawingTemplate> LoadAsync(string id)

View File

@@ -46,6 +46,7 @@ namespace Darkmatter.Features.GameplayFlow.Systems
private string _templateId; private string _templateId;
private DrawingPhase _phase = DrawingPhase.ShapeBuilding; private DrawingPhase _phase = DrawingPhase.ShapeBuilding;
private bool _allContentReported; private bool _allContentReported;
private bool _navigating;
private float _lastThumbnailTime = float.NegativeInfinity; private float _lastThumbnailTime = float.NegativeInfinity;
private IDisposable _assembledSub; private IDisposable _assembledSub;
@@ -80,6 +81,27 @@ namespace Darkmatter.Features.GameplayFlow.Systems
} }
public async UniTask StartAsync(CancellationToken cancellation) public async UniTask StartAsync(CancellationToken cancellation)
{
try
{
await StartCoreAsync(cancellation);
}
catch (OperationCanceledException)
{
// Scene torn down mid-init; nothing to recover.
}
catch (Exception e)
{
// Any throw here (missing template id, failed Addressables load, region init)
// would otherwise skip _loadingScreen.Hide() and strand the kid on a frozen
// loader forever. Log it and route back to the Colorbook, whose flow hides the
// loading screen once its catalog is populated.
Debug.LogError($"[GameplayFlow] Failed to start gameplay, returning to Colorbook: {e}");
await RecoverToColorbookAsync();
}
}
private async UniTask StartCoreAsync(CancellationToken cancellation)
{ {
_scopeCts = CancellationTokenSource.CreateLinkedTokenSource(cancellation); _scopeCts = CancellationTokenSource.CreateLinkedTokenSource(cancellation);
var ct = _scopeCts.Token; var ct = _scopeCts.Token;
@@ -127,7 +149,30 @@ namespace Darkmatter.Features.GameplayFlow.Systems
_loadingScreen.Hide(); _loadingScreen.Hide();
} }
private async UniTask RecoverToColorbookAsync()
{
try
{
_shapeBuilder.Clear();
_coloring.Clear();
await _scenes.LoadSceneAsync(nameof(GameScene.Colorbook), progress: null, cancellationToken: default);
await _scenes.UnloadSceneAsync(nameof(GameScene.Gameplay), progress: null, cancellationToken: default);
}
catch (Exception e)
{
Debug.LogError($"[GameplayFlow] Recovery to Colorbook failed: {e}");
_loadingScreen.Hide();
}
}
public async UniTask BackAsync(CancellationToken ct) public async UniTask BackAsync(CancellationToken ct)
{
// A second navigation while one runs (e.g. Back pressed during the completion
// animation inside NextAsync) would interleave two saves and two Colorbook loads,
// and Clear() would rip the celebration apart mid-flight. First navigation wins.
if (_navigating) return;
_navigating = true;
try
{ {
await SaveCurrentAsync(CancellationToken.None); await SaveCurrentAsync(CancellationToken.None);
_loadingScreen.Show(); _loadingScreen.Show();
@@ -136,6 +181,11 @@ namespace Darkmatter.Features.GameplayFlow.Systems
await _scenes.LoadSceneAsync(nameof(GameScene.Colorbook), progress: null, cancellationToken: ct); await _scenes.LoadSceneAsync(nameof(GameScene.Colorbook), progress: null, cancellationToken: ct);
await _scenes.UnloadSceneAsync(nameof(GameScene.Gameplay), progress: null, cancellationToken: ct); await _scenes.UnloadSceneAsync(nameof(GameScene.Gameplay), progress: null, cancellationToken: ct);
} }
finally
{
_navigating = false;
}
}
public async UniTask SaveAsync(CancellationToken ct) public async UniTask SaveAsync(CancellationToken ct)
{ {
@@ -146,6 +196,10 @@ namespace Darkmatter.Features.GameplayFlow.Systems
} }
public async UniTask NextAsync(CancellationToken ct) public async UniTask NextAsync(CancellationToken ct)
{
if (_navigating) return;
_navigating = true;
try
{ {
// Drop any debounced autosave so it can't fire during/after the completion // Drop any debounced autosave so it can't fire during/after the completion
// animation and capture the animation (or an empty paper) into the thumbnail. // animation and capture the animation (or an empty paper) into the thumbnail.
@@ -173,23 +227,19 @@ namespace Darkmatter.Features.GameplayFlow.Systems
} }
var nextId = _catalog.GetNextTemplate(_templateId); var nextId = _catalog.GetNextTemplate(_templateId);
if (string.IsNullOrEmpty(nextId))
{
_loadingScreen.Show();
_shapeBuilder.Clear();
_coloring.Clear();
await _scenes.LoadSceneAsync(nameof(GameScene.Colorbook), progress: null, cancellationToken: default);
await _scenes.UnloadSceneAsync(nameof(GameScene.Gameplay), progress: null, cancellationToken: default);
return;
}
_loadingScreen.Show(); _loadingScreen.Show();
_shapeBuilder.Clear(); _shapeBuilder.Clear();
_coloring.Clear(); _coloring.Clear();
await _scenes.LoadSceneAsync(nameof(GameScene.Colorbook), progress: null, cancellationToken: default); await _scenes.LoadSceneAsync(nameof(GameScene.Colorbook), progress: null, cancellationToken: default);
await _scenes.UnloadSceneAsync(nameof(GameScene.Gameplay), progress: null, cancellationToken: default); await _scenes.UnloadSceneAsync(nameof(GameScene.Gameplay), progress: null, cancellationToken: default);
if (!string.IsNullOrEmpty(nextId))
_bus.Publish(new DrawingSelectedSignal(nextId)); _bus.Publish(new DrawingSelectedSignal(nextId));
} }
finally
{
_navigating = false;
}
}
private void OnShapeAssembled(ShapeAssembledSignal signal) private void OnShapeAssembled(ShapeAssembledSignal signal)
@@ -232,6 +282,10 @@ namespace Darkmatter.Features.GameplayFlow.Systems
} }
private async UniTaskVoid EditAsync(string newTemplateId) private async UniTaskVoid EditAsync(string newTemplateId)
{
if (_navigating) return;
_navigating = true;
try
{ {
await SaveCurrentAsync(CancellationToken.None); await SaveCurrentAsync(CancellationToken.None);
_loadingScreen.Show(); _loadingScreen.Show();
@@ -241,6 +295,11 @@ namespace Darkmatter.Features.GameplayFlow.Systems
await _scenes.UnloadSceneAsync(nameof(GameScene.Gameplay), progress: null, cancellationToken: default); await _scenes.UnloadSceneAsync(nameof(GameScene.Gameplay), progress: null, cancellationToken: default);
_bus.Publish(new DrawingSelectedSignal(newTemplateId)); _bus.Publish(new DrawingSelectedSignal(newTemplateId));
} }
finally
{
_navigating = false;
}
}
private async UniTaskVoid DebouncedAutosaveAsync(CancellationToken ct) private async UniTaskVoid DebouncedAutosaveAsync(CancellationToken ct)
{ {

View File

@@ -13,6 +13,7 @@ namespace Darkmatter.Features.Mainmenu
private readonly IEventBus _eventBus; private readonly IEventBus _eventBus;
private readonly MainMenuView _view; private readonly MainMenuView _view;
private readonly ISfxPlayer _sfxPlayer; private readonly ISfxPlayer _sfxPlayer;
private IDisposable _introSubscription;
public MainMenuPresenter(MainMenuView view, IEventBus eventBus, ISfxPlayer sfxPlayer) public MainMenuPresenter(MainMenuView view, IEventBus eventBus, ISfxPlayer sfxPlayer)
@@ -26,7 +27,7 @@ namespace Darkmatter.Features.Mainmenu
{ {
_view.OnPlayBtnPressedEvent += OnPlayBtnPressed; _view.OnPlayBtnPressedEvent += OnPlayBtnPressed;
_view.OnPlayBtnClickedEvent += OnPlayBtnClicked; _view.OnPlayBtnClickedEvent += OnPlayBtnClicked;
_eventBus.Subscribe<IntroCompletedSignal>(OnIntroComplete); _introSubscription = _eventBus.Subscribe<IntroCompletedSignal>(OnIntroComplete);
} }
private void OnIntroComplete(IntroCompletedSignal signal) private void OnIntroComplete(IntroCompletedSignal signal)
@@ -48,6 +49,7 @@ namespace Darkmatter.Features.Mainmenu
{ {
_view.OnPlayBtnPressedEvent -= OnPlayBtnPressed; _view.OnPlayBtnPressedEvent -= OnPlayBtnPressed;
_view.OnPlayBtnClickedEvent -= OnPlayBtnClicked; _view.OnPlayBtnClickedEvent -= OnPlayBtnClicked;
_introSubscription?.Dispose();
} }
} }
} }

View File

@@ -86,6 +86,12 @@ public sealed class ProgressionRepository
{ {
if (string.IsNullOrEmpty(templateId) || png == null || png.Length == 0) return; if (string.IsNullOrEmpty(templateId) || png == null || png.Length == 0) return;
// Never let thumbnail IO (disk full, permission churn) throw into the save path:
// saves run before every Back/Next navigation, and an unhandled IOException there
// aborts the scene transition and traps the user in gameplay. Losing a thumbnail
// is fine — the colors themselves persist via PlayerPrefs.
try
{
Directory.CreateDirectory(ThumbnailDirectory()); Directory.CreateDirectory(ThumbnailDirectory());
var path = ThumbnailPath(templateId); var path = ThumbnailPath(templateId);
var tmp = path + ".tmp"; var tmp = path + ".tmp";
@@ -97,6 +103,11 @@ public sealed class ProgressionRepository
File.Move(tmp, path); File.Move(tmp, path);
}); });
} }
catch (Exception e)
{
Debug.LogWarning($"[Progression] Failed writing thumbnail '{templateId}': {e.Message}");
}
}
public async UniTask<Texture2D> LoadThumbnailAsync(string templateId) public async UniTask<Texture2D> LoadThumbnailAsync(string templateId)
{ {

View File

@@ -15,7 +15,23 @@ namespace Darkmatter.Libs.Observer
if (_map.TryGetValue(typeof(T), out var handlerObj)) if (_map.TryGetValue(typeof(T), out var handlerObj))
{ {
var action = (Action<T>)handlerObj; var action = (Action<T>)handlerObj;
action?.Invoke(evt); if (action == null) return;
// Invoke handlers one by one: a multicast invoke would let one throwing
// subscriber silently starve every later subscriber and rethrow into the
// publisher (paint / scene-transition code), turning a local bug into a
// stuck flow.
foreach (var handler in action.GetInvocationList())
{
try
{
((Action<T>)handler).Invoke(evt);
}
catch (Exception e)
{
UnityEngine.Debug.LogException(e);
}
}
} }
} }

View File

@@ -350,7 +350,9 @@ namespace Darkmatter.Services.Audio
if (!source.gameObject.activeInHierarchy) source.gameObject.SetActive(true); if (!source.gameObject.activeInHierarchy) source.gameObject.SetActive(true);
source.Play(); source.Play();
#if UNITY_EDITOR || DEVELOPMENT_BUILD
Debug.Log($"[AudioService] Play clip='{(request.Clip != null ? request.Clip.name : "null")}' ch={request.Channel} vol={source.volume:F2} mixer={(source.outputAudioMixerGroup != null ? source.outputAudioMixerGroup.name : "none")} listener={(AudioListener.volume)} muted={AudioListener.pause} isPlaying={source.isPlaying}"); Debug.Log($"[AudioService] Play clip='{(request.Clip != null ? request.Clip.name : "null")}' ch={request.Channel} vol={source.volume:F2} mixer={(source.outputAudioMixerGroup != null ? source.outputAudioMixerGroup.name : "none")} listener={(AudioListener.volume)} muted={AudioListener.pause} isPlaying={source.isPlaying}");
#endif
if (IsChannelPaused(request.Channel)) if (IsChannelPaused(request.Channel))
{ {
source.Pause(); source.Pause();

View File

@@ -72,14 +72,14 @@ namespace Darkmatter.Services.Capture
// GetPixels/SetPixels cropping allocated a managed Color[] at 16 bytes/pixel (tens of MB) // GetPixels/SetPixels cropping allocated a managed Color[] at 16 bytes/pixel (tens of MB)
// on every capture — autosave runs this after each paint, OOM-killing low-RAM devices. // on every capture — autosave runs this after each paint, OOM-killing low-RAM devices.
var cropped = new Texture2D(cropW, cropH, TextureFormat.RGBA32, mipChain: false); var cropped = new Texture2D(cropW, cropH, TextureFormat.RGBA32, mipChain: false);
try
{
cropped.ReadPixels(new Rect(crop.x, crop.y, cropW, cropH), 0, 0); cropped.ReadPixels(new Rect(crop.x, crop.y, cropW, cropH), 0, 0);
cropped.Apply(); cropped.Apply();
RenderTexture.active = prevActive; RenderTexture.active = prevActive;
captureViewScope?.Dispose(); captureViewScope?.Dispose();
captureViewScope = null; captureViewScope = null;
try
{
int targetW = Mathf.Max(1, Mathf.RoundToInt(cropW * scale)); int targetW = Mathf.Max(1, Mathf.RoundToInt(cropW * scale));
int targetH = Mathf.Max(1, Mathf.RoundToInt(cropH * scale)); int targetH = Mathf.Max(1, Mathf.RoundToInt(cropH * scale));
Texture2D output = cropped; Texture2D output = cropped;
@@ -97,17 +97,29 @@ namespace Darkmatter.Services.Capture
} }
finally finally
{ {
RenderTexture.active = prevActive;
Object.Destroy(cropped); Object.Destroy(cropped);
} }
} }
finally finally
{
// The awaits above give scene teardown (autosave racing an unload) a chance to
// destroy the canvas or camera; touching them then would throw from finally and
// skip the rest of the restore.
if (paperCanvas != null)
{ {
paperCanvas.renderMode = prevMode; paperCanvas.renderMode = prevMode;
paperCanvas.worldCamera = prevWorldCam; paperCanvas.worldCamera = prevWorldCam;
paperCanvas.planeDistance = prevPlaneDist; paperCanvas.planeDistance = prevPlaneDist;
}
if (cam != null)
{
cam.targetTexture = prevTarget; cam.targetTexture = prevTarget;
cam.clearFlags = prevFlags; cam.clearFlags = prevFlags;
cam.backgroundColor = prevBg; cam.backgroundColor = prevBg;
}
RenderTexture.ReleaseTemporary(rt); RenderTexture.ReleaseTemporary(rt);
if (disabledGraphics != null) if (disabledGraphics != null)
foreach (var g in disabledGraphics) foreach (var g in disabledGraphics)

View File

@@ -118,8 +118,17 @@ namespace Darkmatter.Services.Gallery
GL.PopMatrix(); GL.PopMatrix();
var output = new Texture2D(bgW, bgH, TextureFormat.RGBA32, mipChain: false); var output = new Texture2D(bgW, bgH, TextureFormat.RGBA32, mipChain: false);
try
{
output.ReadPixels(new Rect(0, 0, bgW, bgH), 0, 0); output.ReadPixels(new Rect(0, 0, bgW, bgH), 0, 0);
output.Apply(); output.Apply();
}
catch
{
UnityEngine.Object.Destroy(output);
throw;
}
return output; return output;
} }
finally finally

Binary file not shown.

View File

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

View File

@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: 52ad8e0dfd52e3045a2ee0230cfd7c26
AudioImporter:
externalObjects: {}
serializedVersion: 8
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: 6eec8891b258c44489104669182fbe6c
AudioImporter:
externalObjects: {}
serializedVersion: 8
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: fd3546b535131d149bd45f94427093e2
AudioImporter:
externalObjects: {}
serializedVersion: 8
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: 36671d416af67c3489ca34f657bc1b8c
AudioImporter:
externalObjects: {}
serializedVersion: 8
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: 89ff39f364accd04ab402f2bab9dc1b2
AudioImporter:
externalObjects: {}
serializedVersion: 8
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: 0a4bf723010b92241ad4291a054d5ca2
AudioImporter:
externalObjects: {}
serializedVersion: 8
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: 3848293c8818eb04c9d965ee879e8d72
AudioImporter:
externalObjects: {}
serializedVersion: 8
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: 12aca729e6b6a0a4fb3236ccbfdb1e68
AudioImporter:
externalObjects: {}
serializedVersion: 8
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: 49f744f643569284ca5ab4d58b1b5125
AudioImporter:
externalObjects: {}
serializedVersion: 8
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: a1500a72608967640bf41da5d6f07de3
AudioImporter:
externalObjects: {}
serializedVersion: 8
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: d8117f7c6d194c94daae65c98116e3a2
AudioImporter:
externalObjects: {}
serializedVersion: 8
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: ba25d8167dbc4314dbd055e9ea54b780
AudioImporter:
externalObjects: {}
serializedVersion: 8
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: 2efdd23565297424cb3de0bcb8d322e7
AudioImporter:
externalObjects: {}
serializedVersion: 8
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: 41c16b48cd11e4a41a420c4f2acd8856
AudioImporter:
externalObjects: {}
serializedVersion: 8
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: 325b6dcfda85c3f4fb56ac89f35295f9
AudioImporter:
externalObjects: {}
serializedVersion: 8
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: 63e00cdfc6be04342a4a7de8d588042e
AudioImporter:
externalObjects: {}
serializedVersion: 8
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: 0f8402fed534dd8419a66c75e0f56cb2
AudioImporter:
externalObjects: {}
serializedVersion: 8
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: 46469a3746e042c4a8beba465e7a48ac
AudioImporter:
externalObjects: {}
serializedVersion: 8
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: d31750440618579428a76dafbdd419b5
AudioImporter:
externalObjects: {}
serializedVersion: 8
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: 60cf0d97e2702294ab3ec0c1c9b6e5ca
AudioImporter:
externalObjects: {}
serializedVersion: 8
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: f7c7a0e561d9b50478c2c749abe1c98f
AudioImporter:
externalObjects: {}
serializedVersion: 8
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: 55ded844f5f6d544ca7cf55c7b13b1f6
AudioImporter:
externalObjects: {}
serializedVersion: 8
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: c092a0b937b987241b84ab180861af36
AudioImporter:
externalObjects: {}
serializedVersion: 8
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: 6680a0605f6666a40b97aa6e62901c0a
AudioImporter:
externalObjects: {}
serializedVersion: 8
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: 8034700aded7a644aaf8dd8c8e168a2e
AudioImporter:
externalObjects: {}
serializedVersion: 8
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: 701d36ef5fd476f469fbb4ead377aba2
AudioImporter:
externalObjects: {}
serializedVersion: 8
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: 936d507515abe7249a5e9aaa0966a09a
AudioImporter:
externalObjects: {}
serializedVersion: 8
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: c83e6d268a50ce9489e52fa8d524f9e9
AudioImporter:
externalObjects: {}
serializedVersion: 8
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: 4aea62d9463f0354e94317d7ce934397
AudioImporter:
externalObjects: {}
serializedVersion: 8
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: a75c2ea3355f4c941ade73d96890e1f3
AudioImporter:
externalObjects: {}
serializedVersion: 8
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: 411f1aef2ba2573498e69f75b52f7cca
AudioImporter:
externalObjects: {}
serializedVersion: 8
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: fad4f4d8e9264de49a150714ef5cb9fc
AudioImporter:
externalObjects: {}
serializedVersion: 8
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: 670493432c10c1b408e1d311dee14213
AudioImporter:
externalObjects: {}
serializedVersion: 8
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: 030177e8bf326524bb2d96f0a89de699
AudioImporter:
externalObjects: {}
serializedVersion: 8
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: b591037f7142c454e96ea16804a872a0
AudioImporter:
externalObjects: {}
serializedVersion: 8
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: cf55ce4839df97c41a19013d703786b3
AudioImporter:
externalObjects: {}
serializedVersion: 8
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: b3110b8fb6cfef942978420ef673896e
AudioImporter:
externalObjects: {}
serializedVersion: 8
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: 8529de56ed4b61b47b6f5f62e349d2b3
AudioImporter:
externalObjects: {}
serializedVersion: 8
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:

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