Compare commits
1 Commits
main
...
40ba0df92d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
40ba0df92d |
BIN
Assets/.DS_Store
vendored
BIN
Assets/.DS_Store
vendored
Binary file not shown.
BIN
Assets/AddressableAssetsData/.DS_Store
vendored
BIN
Assets/AddressableAssetsData/.DS_Store
vendored
Binary file not shown.
BIN
Assets/AppsFlyer/.DS_Store
vendored
BIN
Assets/AppsFlyer/.DS_Store
vendored
Binary file not shown.
BIN
Assets/Confetti FX/.DS_Store
vendored
BIN
Assets/Confetti FX/.DS_Store
vendored
Binary file not shown.
BIN
Assets/Darkmatter/.DS_Store
vendored
BIN
Assets/Darkmatter/.DS_Store
vendored
Binary file not shown.
@@ -9,7 +9,6 @@ namespace Darkmatter.Core.Contracts.Features.DrawingCatalog
|
||||
{
|
||||
string Id { get; }
|
||||
string DisplayName { get; }
|
||||
int Difficulty { get; }
|
||||
Sprite DefaultThumbnail { get; }
|
||||
GameObject DrawingPrefab { get; }
|
||||
GameObject ColoringPrefab { get; }
|
||||
|
||||
@@ -6,12 +6,9 @@ namespace Darkmatter.Core.Contracts.Features.DrawingCatalog
|
||||
{
|
||||
public interface IDrawingTemplateCatalog
|
||||
{
|
||||
UniTask FetchAsync();
|
||||
UniTask FetchAsync();
|
||||
IReadOnlyList<string> AllTemplateIds { get; }
|
||||
// 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<Sprite> GetThumbnailAsync(string id);
|
||||
UniTask<IDrawingTemplate> LoadAsync(string id);
|
||||
void ReleaseAll();
|
||||
string GetNextTemplate(string currentId);
|
||||
|
||||
@@ -11,9 +11,9 @@ namespace Darkmatter.Core.Contracts.Services.Analytics
|
||||
// Onboarding / activation funnel
|
||||
public const string IntroStarted = "intro_started";
|
||||
public const string IntroCompleted = "intro_completed";
|
||||
public const string TutorialStarted = "tutorial_begin";
|
||||
public const string TutorialStarted = "tutorial_started";
|
||||
public const string TutorialStepCompleted = "tutorial_step_completed";
|
||||
public const string TutorialCompleted = "tutorial_complete";
|
||||
public const string TutorialCompleted = "tutorial_completed";
|
||||
public const string PlayClicked = "play_clicked";
|
||||
public const string FirstDrawingStarted = "first_drawing_started";
|
||||
|
||||
@@ -32,9 +32,9 @@ namespace Darkmatter.Core.Contracts.Services.Analytics
|
||||
public const string ShapeAssembled = "shape_assembled";
|
||||
public const string ColorApplied = "color_applied";
|
||||
|
||||
// Progression & content. level_start and level_end are GA4-recommended games events.
|
||||
// Progression & content (GA4 recommended level_start / level_complete)
|
||||
public const string LevelStart = "level_start";
|
||||
public const string LevelComplete = "level_end";
|
||||
public const string LevelComplete = "level_complete";
|
||||
public const string AllContentCompleted = "all_content_completed";
|
||||
|
||||
// Gallery capture (pre-existing)
|
||||
|
||||
@@ -4,14 +4,14 @@ namespace Darkmatter.Core.Contracts.Services.Analytics
|
||||
/// Canonical analytics parameter keys. Pass a consistent set on every gameplay event so reports can
|
||||
/// answer "which drawing did people quit on", not just "how many quit". Each key used in reports must
|
||||
/// be registered as a Custom Dimension in the Firebase console. GA4 limit: 25 params per event.
|
||||
/// GA4-recommended events (level_start/level_end/ad_impression) reuse GA4's reserved param names
|
||||
/// GA4-recommended events (level_start/level_complete/ad_impression) reuse GA4's reserved param names
|
||||
/// (level_name, success, value, currency, ad_platform, ad_format, ad_unit_name) so they're recognised.
|
||||
/// </summary>
|
||||
public static class AnalyticsParams
|
||||
{
|
||||
// Content identity
|
||||
public const string DrawingId = "drawing_id";
|
||||
public const string LevelName = "level_name"; // GA4 reserved (level_start/level_end)
|
||||
public const string LevelName = "level_name"; // GA4 reserved (level_start/level_complete)
|
||||
|
||||
// Tutorial
|
||||
public const string StepId = "step_id";
|
||||
@@ -23,7 +23,7 @@ namespace Darkmatter.Core.Contracts.Services.Analytics
|
||||
public const string Attempts = "attempts";
|
||||
public const string ApplyIndex = "apply_index";
|
||||
public const string CompletionCount = "completion_count";
|
||||
public const string Success = "success"; // GA4 reserved (level_end)
|
||||
public const string Success = "success"; // GA4 reserved (level_complete)
|
||||
|
||||
// Monetization (GA4 ad_impression reserved names)
|
||||
public const string Value = "value";
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
namespace Darkmatter.Core.Data.Signals.Features.Capture
|
||||
{
|
||||
/// <summary>
|
||||
/// TemplateId identifies which drawing was saved; publishers outside gameplay (art book) must set it
|
||||
/// because there is no active drawing for analytics to fall back on there.
|
||||
/// </summary>
|
||||
public record struct GallerySaveCompletedSignal(bool Success, string TemplateId = null);
|
||||
public record struct GallerySaveCompletedSignal(bool Success);
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
namespace Darkmatter.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// The drawing-catalog (colorbook) screen became visible: scene entry (play / back / next) or
|
||||
/// returning from the art book. Distinct from <see cref="OpenColorBookSignal"/>, which is the
|
||||
/// command to show the view and only fires on one navigation path. Analytics keys colorbook_opened
|
||||
/// and abandoned-drawing detection off this.
|
||||
/// </summary>
|
||||
public record struct ColorbookShownSignal;
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fa0fb32346ff54da3a9bbfec9f763cf2
|
||||
@@ -12,8 +12,6 @@ namespace Darkmatter.Core.Data.Static.Features.DrawingTemplate
|
||||
{
|
||||
[SerializeField] private string id;
|
||||
[SerializeField] private string displayName;
|
||||
[SerializeField, Min(0), Tooltip("0 = auto (pieces + regions). Any positive value overrides the computed difficulty.")]
|
||||
private int difficultyOverride;
|
||||
[SerializeField] private Sprite defaultThumbnail;
|
||||
[SerializeField] private GameObject drawingPrefab;
|
||||
[SerializeField] private GameObject coloringPrefab;
|
||||
@@ -24,11 +22,6 @@ namespace Darkmatter.Core.Data.Static.Features.DrawingTemplate
|
||||
|
||||
public string Id => id;
|
||||
public string DisplayName => displayName;
|
||||
// Workload-based: every shape to place and region to color adds one point.
|
||||
// A positive difficultyOverride replaces the computed value.
|
||||
public int AutoDifficulty => pieces.Count + regions.Count;
|
||||
public int Difficulty => difficultyOverride > 0 ? difficultyOverride : AutoDifficulty;
|
||||
public bool HasDifficultyOverride => difficultyOverride > 0;
|
||||
public Sprite DefaultThumbnail => defaultThumbnail;
|
||||
public GameObject DrawingPrefab => drawingPrefab;
|
||||
public GameObject ColoringPrefab => coloringPrefab;
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
"GUID:c1c03c0e5b2f4412b9f2be1c20d6a9b1",
|
||||
"GUID:b4c9f7fbf1e144933a1797dc208ece5f",
|
||||
"GUID:b0214a6008ed146ff8f122a6a9c2f6cc",
|
||||
"GUID:f51ebe6a0ceec4240a699833d6309b23",
|
||||
"GUID:75469ad4d38634e559750d17036d5f7c"
|
||||
"GUID:f51ebe6a0ceec4240a699833d6309b23"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ee0bbb69e2ec44637a7f5fb6f18a2518
|
||||
@@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Darkmatter.Core.Contracts.Features.Progression;
|
||||
@@ -15,10 +14,6 @@ namespace Darkmatter.Features.AppBoot.Flow
|
||||
{
|
||||
public class AppBootFlow : IAsyncStartable
|
||||
{
|
||||
private const int IntroPrepareTimeoutMs = 3000;
|
||||
private const float IntroMinimumFallbackSeconds = 6f;
|
||||
private const float IntroFallbackPaddingSeconds = 2f;
|
||||
|
||||
private readonly AppBootSceneRefs _sceneRefs;
|
||||
private readonly ISceneService _sceneService;
|
||||
private readonly IEventBus _eventBus;
|
||||
@@ -35,96 +30,36 @@ namespace Darkmatter.Features.AppBoot.Flow
|
||||
|
||||
public async UniTask StartAsync(CancellationToken cancellation = default)
|
||||
{
|
||||
// Entry points run in serviceModules registration order and this module is near the front.
|
||||
// Everything below LoadAsync completes synchronously, so without a real yield the
|
||||
// IntroStartedSignal publish would happen before later-registered subscribers
|
||||
// (e.g. AnalyticsTracker) have run Start() — the event bus has no replay, so they'd miss it.
|
||||
await UniTask.Yield();
|
||||
|
||||
#if UNITY_ANDROID || UNITY_IOS
|
||||
Application.targetFrameRate = 60;
|
||||
#endif
|
||||
Screen.sleepTimeout = SleepTimeout.NeverSleep;
|
||||
await _progression.LoadAsync();
|
||||
|
||||
var tcs = new UniTaskCompletionSource();
|
||||
var player = _sceneRefs.IntroVideoPlayer;
|
||||
var introTask = PlayIntroAsync(player, cancellation);
|
||||
|
||||
void OnDone(VideoPlayer vp)
|
||||
{
|
||||
vp.loopPointReached -= OnDone;
|
||||
tcs.TrySetResult();
|
||||
}
|
||||
|
||||
player.loopPointReached += OnDone;
|
||||
player.Play();
|
||||
_eventBus.Publish(new IntroStartedSignal());
|
||||
await _sceneService.LoadSceneAsync(nameof(GameScene.MainMenu), null, cancellation);
|
||||
|
||||
await introTask.AttachExternalCancellation(cancellation);
|
||||
|
||||
CleanupIntro(player);
|
||||
if (_sceneRefs.IntroCanvas != null)
|
||||
UnityEngine.Object.Destroy(_sceneRefs.IntroCanvas.gameObject);
|
||||
_eventBus.Publish(new IntroCompletedSignal());
|
||||
}
|
||||
|
||||
private async UniTask PlayIntroAsync(VideoPlayer player, CancellationToken cancellation)
|
||||
{
|
||||
if (player == null)
|
||||
return;
|
||||
|
||||
var prepared = new UniTaskCompletionSource();
|
||||
var completed = new UniTaskCompletionSource();
|
||||
|
||||
void OnPrepared(VideoPlayer vp) => prepared.TrySetResult();
|
||||
|
||||
void OnDone(VideoPlayer vp) => completed.TrySetResult();
|
||||
|
||||
void OnError(VideoPlayer vp, string message)
|
||||
{
|
||||
Debug.LogWarning($"[AppBootFlow] Intro video error: {message}");
|
||||
completed.TrySetResult();
|
||||
prepared.TrySetResult();
|
||||
}
|
||||
|
||||
player.prepareCompleted += OnPrepared;
|
||||
player.loopPointReached += OnDone;
|
||||
player.errorReceived += OnError;
|
||||
|
||||
try
|
||||
{
|
||||
if (!player.isPrepared)
|
||||
{
|
||||
player.Prepare();
|
||||
await UniTask.WhenAny(
|
||||
prepared.Task,
|
||||
UniTask.Delay(IntroPrepareTimeoutMs, cancellationToken: cancellation));
|
||||
}
|
||||
|
||||
player.Play();
|
||||
_eventBus.Publish(new IntroStartedSignal());
|
||||
|
||||
await UniTask.WhenAny(
|
||||
completed.Task,
|
||||
UniTask.Delay(GetIntroFallbackTimeout(player), cancellationToken: cancellation));
|
||||
}
|
||||
finally
|
||||
{
|
||||
player.prepareCompleted -= OnPrepared;
|
||||
player.loopPointReached -= OnDone;
|
||||
player.errorReceived -= OnError;
|
||||
}
|
||||
}
|
||||
|
||||
private static TimeSpan GetIntroFallbackTimeout(VideoPlayer player)
|
||||
{
|
||||
var length = player != null && player.length > 0d
|
||||
? (float)player.length
|
||||
: IntroMinimumFallbackSeconds;
|
||||
|
||||
return TimeSpan.FromSeconds(Mathf.Max(IntroMinimumFallbackSeconds, length + IntroFallbackPaddingSeconds));
|
||||
}
|
||||
|
||||
private static void CleanupIntro(VideoPlayer player)
|
||||
{
|
||||
if (player == null)
|
||||
return;
|
||||
await tcs.Task.AttachExternalCancellation(cancellation);
|
||||
|
||||
player.Stop();
|
||||
var rt = player.targetTexture;
|
||||
if (rt != null) rt.Release();
|
||||
UnityEngine.Object.Destroy(player.gameObject);
|
||||
|
||||
if (_sceneRefs.IntroCanvas != null)
|
||||
Object.Destroy(_sceneRefs.IntroCanvas.gameObject);
|
||||
Object.Destroy(player.gameObject);
|
||||
_eventBus.Publish(new IntroCompletedSignal());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,6 @@ namespace Darkmatter.Features.AppBoot.Installers
|
||||
if (sceneRefs != null)
|
||||
builder.RegisterComponent(sceneRefs);
|
||||
builder.RegisterEntryPoint<AppBootFlow>();
|
||||
builder.RegisterEntryPoint<AdaptiveFrameRate>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ using Darkmatter.Core.Contracts.Features.DrawingCatalog;
|
||||
using Darkmatter.Core.Contracts.Features.Progression;
|
||||
using Darkmatter.Core.Contracts.Features.SaveGate;
|
||||
using Darkmatter.Core.Contracts.Services.Gallery;
|
||||
using Darkmatter.Core.Data.Signals.Features.Capture;
|
||||
using Darkmatter.Core.Data.Signals.Features.Drawing;
|
||||
using Darkmatter.Libs.Observer;
|
||||
using UnityEngine;
|
||||
@@ -210,20 +209,7 @@ namespace Darkmatter.Features.Artbook
|
||||
texture = await _progression.GetCachedThumbnailAsync(entry.Value.Id);
|
||||
if (texture == null) return;
|
||||
|
||||
// Mirror CaptureSystem's pairing: Started before the write, Completed(success) in
|
||||
// finally — art-book saves land in the same gallery_save_* analytics funnel as
|
||||
// gameplay saves.
|
||||
_eventBus.Publish(new GallerySaveStartedSignal());
|
||||
var success = false;
|
||||
try
|
||||
{
|
||||
await _gallery.SaveImageAsync(texture, entry.Value.Name, ct);
|
||||
success = true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_eventBus.Publish(new GallerySaveCompletedSignal(success, entry.Value.Id));
|
||||
}
|
||||
await _gallery.SaveImageAsync(texture, entry.Value.Name, ct);
|
||||
// The art book has no success popup of its own, so use the shared toast.
|
||||
await _saveGate.ShowSavedAsync(ct);
|
||||
}
|
||||
|
||||
@@ -60,14 +60,12 @@ namespace Darkmatter.Features.Capture
|
||||
|
||||
var tex = new Texture2D(2, 2, TextureFormat.RGBA32, mipChain: false);
|
||||
var success = false;
|
||||
// Started must pair 1:1 with the Completed publish in finally — a failed LoadImage or a
|
||||
// throw in BuildFileNameAsync would otherwise emit Completed(false) with no Started.
|
||||
_bus.Publish(new GallerySaveStartedSignal());
|
||||
try
|
||||
{
|
||||
if (tex.LoadImage(png))
|
||||
{
|
||||
var fileName = await BuildFileNameAsync();
|
||||
_bus.Publish(new GallerySaveStartedSignal());
|
||||
await _galleryService.SaveImageAsync(tex, fileName, ct);
|
||||
success = true;
|
||||
}
|
||||
@@ -75,7 +73,7 @@ namespace Darkmatter.Features.Capture
|
||||
finally
|
||||
{
|
||||
UnityEngine.Object.Destroy(tex);
|
||||
_bus.Publish(new GallerySaveCompletedSignal(success, _progression.LastOpenedTemplateId));
|
||||
_bus.Publish(new GallerySaveCompletedSignal(success));
|
||||
}
|
||||
return png;
|
||||
}
|
||||
|
||||
@@ -68,13 +68,7 @@ public class ColorbookFlowController : IAsyncStartable, IDisposable
|
||||
// one VContainer cancels on teardown, and the linked _scopeCts may lag the callback order.
|
||||
if (cancellation.IsCancellationRequested || ct.IsCancellationRequested) return;
|
||||
|
||||
if (!_navigatingToGameplay)
|
||||
{
|
||||
_loadingScreen.Hide();
|
||||
// Catalog genuinely on screen — not a transit hop toward gameplay (edit/next relays land
|
||||
// in this scene with _navigatingToGameplay already latched and never reveal the catalog).
|
||||
_bus.Publish(new ColorbookShownSignal());
|
||||
}
|
||||
if (!_navigatingToGameplay) _loadingScreen.Hide();
|
||||
|
||||
PrewarmInterstitialAdAsync().Forget();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
namespace Darkmatter.Features.Coloring.Systems
|
||||
@@ -9,7 +8,6 @@ namespace Darkmatter.Features.Coloring.Systems
|
||||
public sealed class ColoringPinchZoomController : MonoBehaviour
|
||||
{
|
||||
private const float MinimumPinchDistance = 8f;
|
||||
private const float PanZoomEpsilon = 0.0001f;
|
||||
|
||||
[SerializeField] private float minZoom = 1f;
|
||||
[SerializeField] private float maxZoom = 3f;
|
||||
@@ -24,7 +22,6 @@ namespace Darkmatter.Features.Coloring.Systems
|
||||
private float _currentZoom = 1f;
|
||||
private Vector3 _baseScale = Vector3.one;
|
||||
private Vector2 _baseAnchoredPosition;
|
||||
private PanDragRelay _panRelay;
|
||||
|
||||
public void SetTarget(RectTransform target)
|
||||
{
|
||||
@@ -37,26 +34,6 @@ namespace Darkmatter.Features.Coloring.Systems
|
||||
_baseAnchoredPosition = target != null ? target.anchoredPosition : Vector2.zero;
|
||||
_currentZoom = 1f;
|
||||
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()
|
||||
@@ -69,7 +46,6 @@ namespace Darkmatter.Features.Coloring.Systems
|
||||
|
||||
_currentZoom = 1f;
|
||||
_isPinching = false;
|
||||
UpdatePanRelayState();
|
||||
}
|
||||
|
||||
public IDisposable UseNonZoomedView()
|
||||
@@ -102,7 +78,6 @@ namespace Darkmatter.Features.Coloring.Systems
|
||||
|
||||
_isPinching = false;
|
||||
TryApplyScrollZoom();
|
||||
TryApplyMousePan();
|
||||
}
|
||||
|
||||
private bool TryApplyPinchZoom()
|
||||
@@ -136,16 +111,8 @@ namespace Darkmatter.Features.Coloring.Systems
|
||||
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);
|
||||
ApplyZoomPan(previousMidpointLocal, midpointLocal, targetZoom);
|
||||
ZoomTowards(midpointLocal, targetZoom);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -185,123 +152,12 @@ namespace Darkmatter.Features.Coloring.Systems
|
||||
return;
|
||||
}
|
||||
|
||||
ApplyZoomPan(parentLocalPoint, parentLocalPoint, targetZoom);
|
||||
}
|
||||
|
||||
private void ApplyZoomPan(Vector2 previousLocalPoint, Vector2 currentLocalPoint, float targetZoom)
|
||||
{
|
||||
Vector2 contentPoint = (previousLocalPoint - _target.anchoredPosition) / _currentZoom;
|
||||
Vector2 contentPoint = (parentLocalPoint - _target.anchoredPosition) / _currentZoom;
|
||||
_currentZoom = targetZoom;
|
||||
_target.localScale = _baseScale * _currentZoom;
|
||||
_target.anchoredPosition = ClampPan(currentLocalPoint - contentPoint * _currentZoom);
|
||||
UpdatePanRelayState();
|
||||
}
|
||||
|
||||
// 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));
|
||||
_target.anchoredPosition = Mathf.Approximately(_currentZoom, minZoom)
|
||||
? _baseAnchoredPosition
|
||||
: parentLocalPoint - contentPoint * _currentZoom;
|
||||
}
|
||||
|
||||
private bool TryGetParentLocalPoint(Vector2 screenPoint, out Vector2 localPoint)
|
||||
@@ -377,7 +233,6 @@ namespace Darkmatter.Features.Coloring.Systems
|
||||
_target.anchoredPosition = state.AnchoredPosition;
|
||||
_currentZoom = state.CurrentZoom;
|
||||
_isPinching = false;
|
||||
UpdatePanRelayState();
|
||||
}
|
||||
|
||||
private readonly struct ViewState
|
||||
@@ -430,25 +285,5 @@ namespace Darkmatter.Features.Coloring.Systems
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,8 +57,8 @@ public sealed class DrawingCatalogController : IDrawingCatalogController
|
||||
private void Refresh()
|
||||
{
|
||||
_visible.Clear();
|
||||
// Catalog already orders ids by difficulty (easiest first), then id.
|
||||
_visible.AddRange(_catalog.AllTemplateIds);
|
||||
_visible.Sort(StringComparer.OrdinalIgnoreCase);
|
||||
ListChanged?.Invoke();
|
||||
}
|
||||
}
|
||||
@@ -13,8 +13,8 @@ namespace Darkmatter.Features.DrawingCatalog
|
||||
{
|
||||
Id = id;
|
||||
thumbnail.sprite = thumbnailSprite;
|
||||
button.onClick.RemoveAllListeners();
|
||||
button.onClick.AddListener(onClick);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ namespace Darkmatter.Features.DrawingCatalog
|
||||
|
||||
private int _currentPage = 0;
|
||||
private int _totalPages = 1;
|
||||
private const int RowCount = 2;
|
||||
private const int ItemPerPage = 8; //2 rows x 4 columns
|
||||
|
||||
private readonly DrawingCatalogView _view;
|
||||
@@ -25,12 +24,6 @@ namespace Darkmatter.Features.DrawingCatalog
|
||||
private readonly CancellationTokenSource _cts = new();
|
||||
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,
|
||||
IDrawingTemplateCatalog catalog, IEventBus eventBus)
|
||||
{
|
||||
@@ -55,14 +48,8 @@ namespace Darkmatter.Features.DrawingCatalog
|
||||
}
|
||||
|
||||
private void HandleOpenColorBookSignal(OpenColorBookSignal signal)
|
||||
{
|
||||
ShowCatalog();
|
||||
}
|
||||
|
||||
private void ShowCatalog()
|
||||
{
|
||||
_view.Show();
|
||||
_eventBus.Publish(new ColorbookShownSignal());
|
||||
}
|
||||
|
||||
private void OnRightPageBtnClicked()
|
||||
@@ -97,7 +84,7 @@ namespace Darkmatter.Features.DrawingCatalog
|
||||
|
||||
private void OnArtBookBtnClicked()
|
||||
{
|
||||
_eventBus.Publish(new OpenArtBookSignal(ShowCatalog));
|
||||
_eventBus.Publish(new OpenArtBookSignal(()=> _view.Show()));
|
||||
_view.Hide();
|
||||
}
|
||||
|
||||
@@ -115,86 +102,29 @@ namespace Darkmatter.Features.DrawingCatalog
|
||||
|
||||
private async UniTask RefreshAsync(CancellationToken ct)
|
||||
{
|
||||
var newSprites = new List<Sprite>();
|
||||
var newTextures = new List<Texture2D>();
|
||||
var applied = false;
|
||||
var ids = _controller.VisibleIds;
|
||||
var vms = new List<CatalogItemVM>(ids.Count);
|
||||
|
||||
try
|
||||
_totalPages = (int)Math.Ceiling(ids.Count / (float)ItemPerPage);
|
||||
_currentPage = 0;
|
||||
|
||||
foreach (var id in ids)
|
||||
{
|
||||
var ids = ToFixedRowGridOrder(_controller.VisibleIds);
|
||||
var vms = new List<CatalogItemVM>(ids.Count);
|
||||
|
||||
_totalPages = (int)Math.Ceiling(ids.Count / (float)ItemPerPage);
|
||||
_currentPage = 0;
|
||||
|
||||
foreach (var id in ids)
|
||||
{
|
||||
if (ct.IsCancellationRequested) return;
|
||||
var (thumb, owned) = await _catalog.GetThumbnailAsync(id);
|
||||
if (owned)
|
||||
{
|
||||
newSprites.Add(thumb);
|
||||
newTextures.Add(thumb.texture);
|
||||
}
|
||||
|
||||
vms.Add(new CatalogItemVM(id, thumb));
|
||||
}
|
||||
|
||||
if (ct.IsCancellationRequested) return;
|
||||
_view.ResetScrollPosition();
|
||||
_view.SetItems(vms);
|
||||
_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.
|
||||
_controller.NotifyPopulated();
|
||||
|
||||
// Cue the first-run tutorial that the catalog grid is on screen and tappable.
|
||||
_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)
|
||||
{
|
||||
var ordered = new List<string>(ids.Count);
|
||||
|
||||
for (var row = 0; row < RowCount; row++)
|
||||
{
|
||||
for (var index = row; index < ids.Count; index += RowCount)
|
||||
ordered.Add(ids[index]);
|
||||
var thumb = await _catalog.GetThumbnailAsync(id);
|
||||
vms.Add(new CatalogItemVM(id, thumb));
|
||||
}
|
||||
|
||||
return ordered;
|
||||
if (ct.IsCancellationRequested) return;
|
||||
_view.ResetScrollPosition();
|
||||
_view.SetItems(vms);
|
||||
_view.SetPagination(_currentPage, _totalPages);
|
||||
|
||||
// Unblock InitializeAsync: items are now on screen, so the loading screen can hide.
|
||||
_controller.NotifyPopulated();
|
||||
|
||||
// Cue the first-run tutorial that the catalog grid is on screen and tappable.
|
||||
_eventBus.Publish(new DrawingCatalogReadySignal());
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
@@ -210,7 +140,6 @@ namespace Darkmatter.Features.DrawingCatalog
|
||||
_openSubscription?.Dispose();
|
||||
_cts.Cancel();
|
||||
_cts.Dispose();
|
||||
DestroyOwnedThumbnails();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,6 +62,9 @@ namespace Darkmatter.Features.DrawingCatalog
|
||||
while (_buttons.Count < items.Count)
|
||||
{
|
||||
var button = Instantiate(buttonPrefab, content);
|
||||
var item = items[_buttons.Count];
|
||||
button.Initialize(item.Id, item.Thumbnail,
|
||||
() => { OnItemClicked?.Invoke(item.Id); });
|
||||
_buttons.Add(button);
|
||||
}
|
||||
|
||||
@@ -72,13 +75,6 @@ namespace Darkmatter.Features.DrawingCatalog
|
||||
Destroy(last.gameObject);
|
||||
}
|
||||
|
||||
for (var i = 0; i < items.Count; i++)
|
||||
{
|
||||
var item = items[i];
|
||||
_buttons[i].Initialize(item.Id, item.Thumbnail,
|
||||
() => { OnItemClicked?.Invoke(item.Id); });
|
||||
}
|
||||
|
||||
LayoutRebuilder.ForceRebuildLayoutImmediate(content);
|
||||
}
|
||||
|
||||
@@ -182,4 +178,4 @@ namespace Darkmatter.Features.DrawingCatalog
|
||||
_suppressScrollEvents = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,9 +68,7 @@ namespace Darkmatter.Features.DrawingTemplates.Editor
|
||||
_templates.Clear();
|
||||
_shapes.Clear();
|
||||
_palettes.Clear();
|
||||
_templates.AddRange(FindAllOfType<DrawingTemplateSO>()
|
||||
.OrderBy(t => t.Difficulty)
|
||||
.ThenBy(t => t.Id, StringComparer.OrdinalIgnoreCase));
|
||||
_templates.AddRange(FindAllOfType<DrawingTemplateSO>());
|
||||
_shapes.AddRange(FindAllOfType<ShapeSO>());
|
||||
_palettes.AddRange(FindAllOfType<ColorPaletteSO>());
|
||||
}
|
||||
@@ -171,7 +169,7 @@ namespace Darkmatter.Features.DrawingTemplates.Editor
|
||||
using (new EditorGUILayout.VerticalScope())
|
||||
{
|
||||
GUILayout.Label(string.IsNullOrEmpty(t.DisplayName) ? t.name : t.DisplayName, EditorStyles.boldLabel);
|
||||
GUILayout.Label($"id: {(string.IsNullOrEmpty(t.Id) ? "<unset>" : t.Id)} • diff: {t.Difficulty}{(t.HasDifficultyOverride ? "*" : $" ({t.Pieces.Count}p+{t.AuthoredRegions.Count}r)")}", EditorStyles.miniLabel);
|
||||
GUILayout.Label("id: " + (string.IsNullOrEmpty(t.Id) ? "<unset>" : t.Id), EditorStyles.miniLabel);
|
||||
}
|
||||
if (GUILayout.Button("Select", GUILayout.Width(54)))
|
||||
Select(t);
|
||||
@@ -208,13 +206,6 @@ namespace Darkmatter.Features.DrawingTemplates.Editor
|
||||
EditorGUILayout.LabelField("Basics", EditorStyles.boldLabel);
|
||||
EditorGUILayout.PropertyField(so.FindProperty("id"), new GUIContent("Id (Addressable Key)"));
|
||||
EditorGUILayout.PropertyField(so.FindProperty("displayName"));
|
||||
EditorGUILayout.PropertyField(so.FindProperty("difficultyOverride"),
|
||||
new GUIContent("Difficulty Override", "0 = auto (pieces + regions). Any positive value overrides."));
|
||||
EditorGUILayout.LabelField(
|
||||
new GUIContent("Difficulty (effective)", "Sorts the catalog, lowest first."),
|
||||
new GUIContent(t.HasDifficultyOverride
|
||||
? $"{t.Difficulty} (override; auto would be {t.AutoDifficulty})"
|
||||
: $"{t.Difficulty} (auto: {t.Pieces.Count} pieces + {t.AuthoredRegions.Count} regions)"));
|
||||
EditorGUILayout.PropertyField(so.FindProperty("defaultThumbnail"));
|
||||
|
||||
EditorGUILayout.Space(6);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Cysharp.Threading.Tasks;
|
||||
@@ -49,11 +48,11 @@ namespace Darkmatter.Features.DrawingTemplates.Systems
|
||||
_byId[t.Id] = t;
|
||||
}
|
||||
|
||||
SortIds();
|
||||
_allIds.Sort();
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
public async UniTask<(Sprite sprite, bool owned)> GetThumbnailAsync(string id)
|
||||
public async UniTask<Sprite> GetThumbnailAsync(string id)
|
||||
{
|
||||
if (!_byId.TryGetValue(id, out var t))
|
||||
throw new KeyNotFoundException($"Template '{id}' not in catalog. Did InitializeAsync run?");
|
||||
@@ -62,9 +61,9 @@ namespace Darkmatter.Features.DrawingTemplates.Systems
|
||||
if (savedTex != null)
|
||||
{
|
||||
var rect = new Rect(0, 0, savedTex.width, savedTex.height);
|
||||
return (Sprite.Create(savedTex, rect, new Vector2(0.5f, 0.5f)), true);
|
||||
return Sprite.Create(savedTex, rect, new Vector2(0.5f, 0.5f));
|
||||
}
|
||||
return (t.DefaultThumbnail, false);
|
||||
return t.DefaultThumbnail;
|
||||
}
|
||||
|
||||
public UniTask<IDrawingTemplate> LoadAsync(string id)
|
||||
@@ -85,22 +84,12 @@ namespace Darkmatter.Features.DrawingTemplates.Systems
|
||||
if (!_allIds.Contains(id))
|
||||
{
|
||||
_allIds.Add(id);
|
||||
SortIds();
|
||||
_allIds.Sort();
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
// Easiest drawings first; ties broken by id so the order is stable.
|
||||
private void SortIds()
|
||||
{
|
||||
_allIds.Sort((a, b) =>
|
||||
{
|
||||
var byDifficulty = _byId[a].Difficulty.CompareTo(_byId[b].Difficulty);
|
||||
return byDifficulty != 0 ? byDifficulty : string.Compare(a, b, StringComparison.OrdinalIgnoreCase);
|
||||
});
|
||||
}
|
||||
|
||||
public void ReleaseAll()
|
||||
{
|
||||
if (!_initialized) return;
|
||||
|
||||
@@ -2,7 +2,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Darkmatter.Core;
|
||||
using Darkmatter.Core.Contracts.Features.Capture;
|
||||
using Darkmatter.Core.Contracts.Features.Coloring;
|
||||
using Darkmatter.Core.Contracts.Features.DrawingCatalog;
|
||||
@@ -29,7 +28,6 @@ namespace Darkmatter.Features.GameplayFlow.Systems
|
||||
public sealed class GameplayFlowController : IAsyncStartable, IGameplayFlowController, IDisposable
|
||||
{
|
||||
private const int AutosaveDebounceMs = 500;
|
||||
private const float ThumbnailMinIntervalSec = 30f;
|
||||
|
||||
private readonly IProgressionSystem _progression;
|
||||
private readonly IDrawingTemplateCatalog _catalog;
|
||||
@@ -46,12 +44,10 @@ namespace Darkmatter.Features.GameplayFlow.Systems
|
||||
private string _templateId;
|
||||
private DrawingPhase _phase = DrawingPhase.ShapeBuilding;
|
||||
private bool _allContentReported;
|
||||
private float _lastThumbnailTime = float.NegativeInfinity;
|
||||
|
||||
private IDisposable _assembledSub;
|
||||
private IDisposable _colorAppliedSub;
|
||||
private IDisposable _drawingSelectedSub;
|
||||
private IDisposable _openColorbookSub;
|
||||
private CancellationTokenSource _autosaveCts;
|
||||
private CancellationTokenSource _scopeCts;
|
||||
|
||||
@@ -80,27 +76,6 @@ namespace Darkmatter.Features.GameplayFlow.Systems
|
||||
}
|
||||
|
||||
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);
|
||||
var ct = _scopeCts.Token;
|
||||
@@ -129,10 +104,6 @@ namespace Darkmatter.Features.GameplayFlow.Systems
|
||||
_assembledSub = _bus.Subscribe<ShapeAssembledSignal>(OnShapeAssembled);
|
||||
_colorAppliedSub = _bus.Subscribe<ColorAppliedSignal>(OnColorApplied);
|
||||
_drawingSelectedSub = _bus.Subscribe<DrawingSelectedSignal>(OnDrawingSelected);
|
||||
// The art book's "colorbook" button publishes OpenColorBookSignal. In the Colorbook
|
||||
// scene DrawingCatalogPresenter shows the catalog; during gameplay that presenter is
|
||||
// dead, so treat the signal as "leave gameplay for the colorbook" (same as back).
|
||||
_openColorbookSub = _bus.Subscribe<OpenColorBookSignal>(OnOpenColorBook);
|
||||
|
||||
_loadingScreen.SetProgress(1f);
|
||||
if (_phase == DrawingPhase.Coloring)
|
||||
@@ -148,22 +119,6 @@ namespace Darkmatter.Features.GameplayFlow.Systems
|
||||
_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)
|
||||
{
|
||||
await SaveCurrentAsync(CancellationToken.None);
|
||||
@@ -176,10 +131,8 @@ namespace Darkmatter.Features.GameplayFlow.Systems
|
||||
|
||||
public async UniTask SaveAsync(CancellationToken ct)
|
||||
{
|
||||
// One capture serves both the gallery write and the progress thumbnail — this used to
|
||||
// run the full capture pipeline twice back to back.
|
||||
var png = await _capture.CapturePngAsync(saveToGallery: true, ct);
|
||||
await SaveCurrentAsync(ct, captureThumbnail: false, precapturedThumbnail: png);
|
||||
await SaveCurrentAsync(ct);
|
||||
await _capture.CapturePngAsync(saveToGallery: true, ct);
|
||||
}
|
||||
|
||||
public async UniTask NextAsync(CancellationToken ct)
|
||||
@@ -256,11 +209,6 @@ namespace Darkmatter.Features.GameplayFlow.Systems
|
||||
DebouncedAutosaveAsync(_autosaveCts.Token).Forget();
|
||||
}
|
||||
|
||||
private void OnOpenColorBook(OpenColorBookSignal _)
|
||||
{
|
||||
BackAsync(CancellationToken.None).Forget();
|
||||
}
|
||||
|
||||
private void OnDrawingSelected(DrawingSelectedSignal signal)
|
||||
{
|
||||
if (string.IsNullOrEmpty(signal.TemplateId)) return;
|
||||
@@ -284,13 +232,7 @@ namespace Darkmatter.Features.GameplayFlow.Systems
|
||||
try
|
||||
{
|
||||
await UniTask.Delay(AutosaveDebounceMs, cancellationToken: ct);
|
||||
// Autosaves fire after every paint pause; capturing a thumbnail each time renders the
|
||||
// scene to an RT and PNG-encodes it, which OOM-kills long sessions on low-RAM devices.
|
||||
// Colors always persist; the thumbnail refreshes at most every ThumbnailMinIntervalSec
|
||||
// here and unconditionally on the exit paths (back / next / select-away).
|
||||
bool refreshThumbnail =
|
||||
Time.realtimeSinceStartup - _lastThumbnailTime >= ThumbnailMinIntervalSec;
|
||||
await SaveCurrentAsync(ct, captureThumbnail: refreshThumbnail);
|
||||
await SaveCurrentAsync(ct);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
@@ -298,8 +240,7 @@ namespace Darkmatter.Features.GameplayFlow.Systems
|
||||
}
|
||||
}
|
||||
|
||||
private async UniTask SaveCurrentAsync(CancellationToken ct, bool captureThumbnail = true,
|
||||
byte[] precapturedThumbnail = null)
|
||||
private async UniTask SaveCurrentAsync(CancellationToken ct)
|
||||
{
|
||||
if (string.IsNullOrEmpty(_templateId)) return;
|
||||
|
||||
@@ -322,10 +263,7 @@ namespace Darkmatter.Features.GameplayFlow.Systems
|
||||
FirstCompletedUtc = existing?.FirstCompletedUtc,
|
||||
};
|
||||
|
||||
byte[] thumbnailPng = precapturedThumbnail;
|
||||
if (thumbnailPng == null && captureThumbnail)
|
||||
thumbnailPng = await _capture.CapturePngAsync(saveToGallery: false, ct);
|
||||
if (thumbnailPng != null) _lastThumbnailTime = Time.realtimeSinceStartup;
|
||||
var thumbnailPng = await _capture.CapturePngAsync(saveToGallery: false, ct);
|
||||
|
||||
await _progression.SaveProgressAsync(progress, thumbnailPng);
|
||||
}
|
||||
@@ -346,7 +284,6 @@ namespace Darkmatter.Features.GameplayFlow.Systems
|
||||
_assembledSub?.Dispose();
|
||||
_colorAppliedSub?.Dispose();
|
||||
_drawingSelectedSub?.Dispose();
|
||||
_openColorbookSub?.Dispose();
|
||||
_autosaveCts?.Cancel();
|
||||
_autosaveCts?.Dispose();
|
||||
_scopeCts?.Cancel();
|
||||
|
||||
@@ -13,7 +13,6 @@ namespace Darkmatter.Features.Mainmenu
|
||||
private readonly IEventBus _eventBus;
|
||||
private readonly MainMenuView _view;
|
||||
private readonly ISfxPlayer _sfxPlayer;
|
||||
private IDisposable _introSubscription;
|
||||
|
||||
|
||||
public MainMenuPresenter(MainMenuView view, IEventBus eventBus, ISfxPlayer sfxPlayer)
|
||||
@@ -27,7 +26,7 @@ namespace Darkmatter.Features.Mainmenu
|
||||
{
|
||||
_view.OnPlayBtnPressedEvent += OnPlayBtnPressed;
|
||||
_view.OnPlayBtnClickedEvent += OnPlayBtnClicked;
|
||||
_introSubscription = _eventBus.Subscribe<IntroCompletedSignal>(OnIntroComplete);
|
||||
_eventBus.Subscribe<IntroCompletedSignal>(OnIntroComplete);
|
||||
}
|
||||
|
||||
private void OnIntroComplete(IntroCompletedSignal signal)
|
||||
@@ -49,7 +48,6 @@ namespace Darkmatter.Features.Mainmenu
|
||||
{
|
||||
_view.OnPlayBtnPressedEvent -= OnPlayBtnPressed;
|
||||
_view.OnPlayBtnClickedEvent -= OnPlayBtnClicked;
|
||||
_introSubscription?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -86,27 +86,16 @@ public sealed class ProgressionRepository
|
||||
{
|
||||
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());
|
||||
var path = ThumbnailPath(templateId);
|
||||
var tmp = path + ".tmp";
|
||||
Directory.CreateDirectory(ThumbnailDirectory());
|
||||
var path = ThumbnailPath(templateId);
|
||||
var tmp = path + ".tmp";
|
||||
|
||||
await UniTask.RunOnThreadPool(() =>
|
||||
{
|
||||
File.WriteAllBytes(tmp, png);
|
||||
if (File.Exists(path)) File.Delete(path);
|
||||
File.Move(tmp, path);
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
await UniTask.RunOnThreadPool(() =>
|
||||
{
|
||||
Debug.LogWarning($"[Progression] Failed writing thumbnail '{templateId}': {e.Message}");
|
||||
}
|
||||
File.WriteAllBytes(tmp, png);
|
||||
if (File.Exists(path)) File.Delete(path);
|
||||
File.Move(tmp, path);
|
||||
});
|
||||
}
|
||||
|
||||
public async UniTask<Texture2D> LoadThumbnailAsync(string templateId)
|
||||
|
||||
@@ -15,23 +15,7 @@ namespace Darkmatter.Libs.Observer
|
||||
if (_map.TryGetValue(typeof(T), out var handlerObj))
|
||||
{
|
||||
var action = (Action<T>)handlerObj;
|
||||
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);
|
||||
}
|
||||
}
|
||||
action?.Invoke(evt);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,11 +39,6 @@ namespace Darkmatter.Services.Ads
|
||||
[SerializeField, Min(0)]
|
||||
private int maxInterstitialsPerSession = 8;
|
||||
|
||||
[Tooltip(
|
||||
"Enable only after verifying Firebase/AdMob is not already auto-logging ad_impression for the same ads. Leaving this off avoids duplicate ad_impression counts in GA4/Firebase.")]
|
||||
[SerializeField]
|
||||
private bool logManualAdImpressionEvents;
|
||||
|
||||
public bool IsInitialized => _initialized;
|
||||
public event Action<AdFormat, AdLoadState> LoadStateChanged;
|
||||
|
||||
@@ -658,12 +653,11 @@ namespace Darkmatter.Services.Ads
|
||||
ad.OnAdClicked += () => LogAdClicked(format);
|
||||
}
|
||||
|
||||
// AdMob/Firebase can auto-log ad_impression for linked apps. Manual logging is opt-in so one
|
||||
// shown ad does not become two GA4 ad_impression events.
|
||||
// Ad revenue: GA4-recommended ad_impression carries value+currency from AdMob's paid callback —
|
||||
// this is what surfaces ad revenue in Firebase/GA4. Ad callbacks fire on the Unity main thread
|
||||
// (RaiseAdEventsOnUnityMainThread = true), so logging straight to analytics is safe.
|
||||
private void LogAdImpression(AdFormat format, AdValue value)
|
||||
{
|
||||
if (!logManualAdImpressionEvents) return;
|
||||
|
||||
_analytics?.LogEvent(AnalyticsEvents.AdImpression, new Dictionary<string, object>
|
||||
{
|
||||
[AnalyticsParams.AdPlatform] = "AdMob",
|
||||
@@ -742,4 +736,4 @@ namespace Darkmatter.Services.Ads
|
||||
LoadStateChanged?.Invoke(format, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -56,9 +56,8 @@ namespace Darkmatter.Services.Analytics
|
||||
_subs.Add(_bus.Subscribe<IntroCompletedSignal>(_ => _analytics.LogEvent(AnalyticsEvents.IntroCompleted)));
|
||||
_subs.Add(_bus.Subscribe<PlayBtnClickedSignal>(_ => _analytics.LogEvent(AnalyticsEvents.PlayClicked)));
|
||||
|
||||
// Navigation. ColorbookShownSignal (not OpenColorBookSignal, which is the show-view command
|
||||
// and misses scene-entry paths) fires on every way the catalog becomes visible.
|
||||
_subs.Add(_bus.Subscribe<ColorbookShownSignal>(_ => OnColorbookShown()));
|
||||
// Navigation
|
||||
_subs.Add(_bus.Subscribe<OpenColorBookSignal>(_ => _analytics.LogEvent(AnalyticsEvents.ColorbookOpened)));
|
||||
_subs.Add(_bus.Subscribe<OpenArtBookSignal>(_ => _analytics.LogEvent(AnalyticsEvents.ArtbookOpened)));
|
||||
_subs.Add(_bus.Subscribe<ReturnToMainMenuSignal>(_ => OnReturnToMainMenu()));
|
||||
|
||||
@@ -91,10 +90,6 @@ namespace Darkmatter.Services.Analytics
|
||||
|
||||
private void OnDrawingSelected(string drawingId)
|
||||
{
|
||||
// GameplayFlowController relays the selection a second time after the scene swap
|
||||
// (artbook edit path) — same id while already active is that relay, not a new selection.
|
||||
if (drawingId == _activeDrawingId) return;
|
||||
|
||||
// A new selection while a drawing is still open (uncompleted) = the previous one was abandoned.
|
||||
EndActiveDrawingIfAbandoned();
|
||||
|
||||
@@ -161,7 +156,7 @@ namespace Darkmatter.Services.Analytics
|
||||
if (dur >= 0f) p[AnalyticsParams.DurationSeconds] = dur;
|
||||
_analytics.LogEvent(AnalyticsEvents.DrawingCompleted, p);
|
||||
|
||||
// Companion to GA4's level_start.
|
||||
// GA4 recommended games event (lights up built-in level funnels).
|
||||
_analytics.LogEvent(AnalyticsEvents.LevelComplete, new Dictionary<string, object>
|
||||
{
|
||||
[AnalyticsParams.LevelName] = s.TemplateId,
|
||||
@@ -176,24 +171,16 @@ namespace Darkmatter.Services.Analytics
|
||||
if (PlayerPrefs.GetInt(AllContentKey, 0) == 1) return; // once ever
|
||||
PlayerPrefs.SetInt(AllContentKey, 1);
|
||||
PlayerPrefs.Save();
|
||||
_analytics.LogEvent(AnalyticsEvents.AllContentCompleted, new Dictionary<string, object>
|
||||
{
|
||||
[AnalyticsParams.CompletionCount] = s.CompletedCount,
|
||||
});
|
||||
_analytics.LogEvent(AnalyticsEvents.AllContentCompleted,
|
||||
AnalyticsParams.CompletionCount, s.CompletedCount.ToString());
|
||||
}
|
||||
|
||||
private void OnGallerySaveCompleted(GallerySaveCompletedSignal s)
|
||||
{
|
||||
// Prefer the id carried by the signal: art-book saves happen with no active drawing.
|
||||
string drawingId = string.IsNullOrEmpty(s.TemplateId) ? _activeDrawingId ?? string.Empty : s.TemplateId;
|
||||
_analytics.LogEvent(AnalyticsEvents.GallerySaveCompleted, new Dictionary<string, object>
|
||||
{
|
||||
[AnalyticsParams.DrawingId] = drawingId,
|
||||
[AnalyticsParams.Success] = s.Success ? 1 : 0,
|
||||
});
|
||||
_analytics.LogEvent(AnalyticsEvents.GallerySaveCompleted, AnalyticsParams.Success, s.Success ? "true" : "false");
|
||||
_analytics.LogEvent(AnalyticsEvents.DrawingSaved, new Dictionary<string, object>
|
||||
{
|
||||
[AnalyticsParams.DrawingId] = drawingId,
|
||||
[AnalyticsParams.DrawingId] = _activeDrawingId ?? string.Empty,
|
||||
[AnalyticsParams.Success] = s.Success ? 1 : 0,
|
||||
});
|
||||
}
|
||||
@@ -204,15 +191,6 @@ namespace Darkmatter.Services.Analytics
|
||||
_analytics.LogEvent(AnalyticsEvents.MainMenuReturned);
|
||||
}
|
||||
|
||||
private void OnColorbookShown()
|
||||
{
|
||||
// Reaching the catalog with a drawing still open means the player backed out of gameplay
|
||||
// mid-drawing — the abandon moment. (Completion clears the active drawing before the
|
||||
// catalog reloads, so the finish path never lands here with one open.)
|
||||
EndActiveDrawingIfAbandoned();
|
||||
_analytics.LogEvent(AnalyticsEvents.ColorbookOpened);
|
||||
}
|
||||
|
||||
// Emits drawing_abandoned for an open, uncompleted drawing. Completion clears _activeDrawingId, so
|
||||
// reaching here with a non-null id means the drawing was left without finishing — the leak signal.
|
||||
private void EndActiveDrawingIfAbandoned()
|
||||
|
||||
@@ -350,9 +350,7 @@ namespace Darkmatter.Services.Audio
|
||||
if (!source.gameObject.activeInHierarchy) source.gameObject.SetActive(true);
|
||||
|
||||
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}");
|
||||
#endif
|
||||
if (IsChannelPaused(request.Channel))
|
||||
{
|
||||
source.Pause();
|
||||
|
||||
@@ -68,58 +68,54 @@ namespace Darkmatter.Services.Capture
|
||||
|
||||
var prevActive = RenderTexture.active;
|
||||
RenderTexture.active = rt;
|
||||
// Read only the crop region straight off the RT. A full-screen ReadPixels followed by
|
||||
// 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.
|
||||
var cropped = new Texture2D(cropW, cropH, TextureFormat.RGBA32, mipChain: false);
|
||||
var fullScreen = new Texture2D(sw, sh, TextureFormat.RGBA32, mipChain: false);
|
||||
fullScreen.ReadPixels(new Rect(0, 0, sw, sh), 0, 0);
|
||||
fullScreen.Apply();
|
||||
RenderTexture.active = prevActive;
|
||||
captureViewScope?.Dispose();
|
||||
captureViewScope = null;
|
||||
|
||||
try
|
||||
{
|
||||
cropped.ReadPixels(new Rect(crop.x, crop.y, cropW, cropH), 0, 0);
|
||||
cropped.Apply();
|
||||
RenderTexture.active = prevActive;
|
||||
captureViewScope?.Dispose();
|
||||
captureViewScope = null;
|
||||
|
||||
int targetW = Mathf.Max(1, Mathf.RoundToInt(cropW * scale));
|
||||
int targetH = Mathf.Max(1, Mathf.RoundToInt(cropH * scale));
|
||||
Texture2D output = cropped;
|
||||
if (output.width != targetW || output.height != targetH)
|
||||
output = Resize(cropped, targetW, targetH);
|
||||
|
||||
var cropped = new Texture2D(cropW, cropH, TextureFormat.RGBA32, mipChain: false);
|
||||
try
|
||||
{
|
||||
return output.EncodeToPNG();
|
||||
cropped.SetPixels(fullScreen.GetPixels((int)crop.x, (int)crop.y, cropW, cropH));
|
||||
cropped.Apply();
|
||||
|
||||
int targetW = Mathf.Max(1, Mathf.RoundToInt(cropW * scale));
|
||||
int targetH = Mathf.Max(1, Mathf.RoundToInt(cropH * scale));
|
||||
Texture2D output = cropped;
|
||||
if (output.width != targetW || output.height != targetH)
|
||||
output = Resize(cropped, targetW, targetH);
|
||||
|
||||
try
|
||||
{
|
||||
return output.EncodeToPNG();
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (output != cropped) Object.Destroy(output);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (output != cropped) Object.Destroy(output);
|
||||
Object.Destroy(cropped);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
RenderTexture.active = prevActive;
|
||||
Object.Destroy(cropped);
|
||||
Object.Destroy(fullScreen);
|
||||
}
|
||||
}
|
||||
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.worldCamera = prevWorldCam;
|
||||
paperCanvas.planeDistance = prevPlaneDist;
|
||||
}
|
||||
|
||||
if (cam != null)
|
||||
{
|
||||
cam.targetTexture = prevTarget;
|
||||
cam.clearFlags = prevFlags;
|
||||
cam.backgroundColor = prevBg;
|
||||
}
|
||||
|
||||
paperCanvas.renderMode = prevMode;
|
||||
paperCanvas.worldCamera = prevWorldCam;
|
||||
paperCanvas.planeDistance = prevPlaneDist;
|
||||
cam.targetTexture = prevTarget;
|
||||
cam.clearFlags = prevFlags;
|
||||
cam.backgroundColor = prevBg;
|
||||
RenderTexture.ReleaseTemporary(rt);
|
||||
if (disabledGraphics != null)
|
||||
foreach (var g in disabledGraphics)
|
||||
|
||||
@@ -118,17 +118,8 @@ namespace Darkmatter.Services.Gallery
|
||||
GL.PopMatrix();
|
||||
|
||||
var output = new Texture2D(bgW, bgH, TextureFormat.RGBA32, mipChain: false);
|
||||
try
|
||||
{
|
||||
output.ReadPixels(new Rect(0, 0, bgW, bgH), 0, 0);
|
||||
output.Apply();
|
||||
}
|
||||
catch
|
||||
{
|
||||
UnityEngine.Object.Destroy(output);
|
||||
throw;
|
||||
}
|
||||
|
||||
output.ReadPixels(new Rect(0, 0, bgW, bgH), 0, 0);
|
||||
output.Apply();
|
||||
return output;
|
||||
}
|
||||
finally
|
||||
|
||||
BIN
Assets/Darkmatter/Content/.DS_Store
vendored
BIN
Assets/Darkmatter/Content/.DS_Store
vendored
Binary file not shown.
BIN
Assets/Darkmatter/Content/Colorbook UI/.DS_Store
vendored
BIN
Assets/Darkmatter/Content/Colorbook UI/.DS_Store
vendored
Binary file not shown.
Binary file not shown.
@@ -21,7 +21,7 @@ TextureImporter:
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
isReadable: 1
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
|
||||
@@ -21,7 +21,7 @@ TextureImporter:
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
isReadable: 1
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
|
||||
@@ -21,7 +21,7 @@ TextureImporter:
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
isReadable: 1
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
|
||||
@@ -21,7 +21,7 @@ TextureImporter:
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
isReadable: 1
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
|
||||
@@ -21,7 +21,7 @@ TextureImporter:
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
isReadable: 1
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
|
||||
@@ -21,7 +21,7 @@ TextureImporter:
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
isReadable: 1
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -14,7 +14,6 @@ MonoBehaviour:
|
||||
m_EditorClassIdentifier: Core::Darkmatter.Core.Data.Static.Features.DrawingTemplate.DrawingTemplateSO
|
||||
id: Bag
|
||||
displayName: Bag
|
||||
difficultyOverride: 0
|
||||
defaultThumbnail: {fileID: 8766740682603709380, guid: ecad1a66623031f4a91dbcee6f10a295, type: 3}
|
||||
drawingPrefab: {fileID: 8396209140419971077, guid: ab15e2395fec7be429926e140d9f4a61, type: 3}
|
||||
coloringPrefab: {fileID: 222817150442217502, guid: b8b22ebdfe861214c8edef5e46743323, type: 3}
|
||||
|
||||
@@ -23,7 +23,7 @@ MonoBehaviour:
|
||||
m_RequireOpaqueTexture: 0
|
||||
m_OpaqueDownsampling: 1
|
||||
m_SupportsTerrainHoles: 1
|
||||
m_SupportsHDR: 0
|
||||
m_SupportsHDR: 1
|
||||
m_HDRColorBufferPrecision: 0
|
||||
m_MSAA: 1
|
||||
m_RenderScale: 1
|
||||
@@ -42,7 +42,7 @@ MonoBehaviour:
|
||||
m_SupportProbeVolumeScenarioBlending: 0
|
||||
m_ProbeVolumeSHBands: 1
|
||||
m_MainLightRenderingMode: 1
|
||||
m_MainLightShadowsSupported: 0
|
||||
m_MainLightShadowsSupported: 1
|
||||
m_MainLightShadowmapResolution: 2048
|
||||
m_AdditionalLightsRenderingMode: 1
|
||||
m_AdditionalLightsPerObjectLimit: 4
|
||||
|
||||
@@ -977,8 +977,8 @@ RectTransform:
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchoredPosition: {x: 149.13885, y: -50.553192}
|
||||
m_SizeDelta: {x: -336.5826, y: -178.6848}
|
||||
m_AnchoredPosition: {x: 118.53375, y: -50.553192}
|
||||
m_SizeDelta: {x: -305.9775, y: -178.6848}
|
||||
m_Pivot: {x: 0, y: 1}
|
||||
--- !u!114 &1157581244
|
||||
MonoBehaviour:
|
||||
@@ -1743,7 +1743,7 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.GridLayoutGroup
|
||||
m_Padding:
|
||||
m_Left: 0
|
||||
m_Left: 20
|
||||
m_Right: 0
|
||||
m_Top: 0
|
||||
m_Bottom: 0
|
||||
|
||||
BIN
Assets/FacebookSDK/.DS_Store
vendored
BIN
Assets/FacebookSDK/.DS_Store
vendored
Binary file not shown.
BIN
Assets/Firebase/.DS_Store
vendored
BIN
Assets/Firebase/.DS_Store
vendored
Binary file not shown.
BIN
Assets/Packages/.DS_Store
vendored
BIN
Assets/Packages/.DS_Store
vendored
Binary file not shown.
BIN
Assets/Plugins/.DS_Store
vendored
BIN
Assets/Plugins/.DS_Store
vendored
Binary file not shown.
@@ -1 +1 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><resources><string name="com.crashlytics.android.build_id" translatable="false">b468c378-10c7-4fe5-a4e9-6ae4bf5078cd</string></resources>
|
||||
<?xml version="1.0" encoding="utf-8"?><resources><string name="com.crashlytics.android.build_id" translatable="false">8fc48ed6-41de-4dcf-a980-9aa3316fad11</string></resources>
|
||||
|
||||
@@ -13,4 +13,4 @@ MonoBehaviour:
|
||||
m_Name: SmartlookSettings
|
||||
m_EditorClassIdentifier:
|
||||
ProjectKey: 3bcfb098c5c4f6f1f48d8bbef4c0c94d7c2f9bc9
|
||||
FPS: 2
|
||||
FPS: 30
|
||||
|
||||
BIN
Assets/Spine/.DS_Store
vendored
BIN
Assets/Spine/.DS_Store
vendored
Binary file not shown.
BIN
Assets/TextMesh Pro/.DS_Store
vendored
BIN
Assets/TextMesh Pro/.DS_Store
vendored
Binary file not shown.
@@ -8,13 +8,3 @@ ScriptedImporter:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 11500000, guid: 625f186215c104763be7675aa2d941aa, type: 3}
|
||||
useAsTemplate: 0
|
||||
exposeTemplateAsShader: 0
|
||||
indexedData: {instanceID: 0}
|
||||
template:
|
||||
name:
|
||||
category:
|
||||
description:
|
||||
icon: {instanceID: 0}
|
||||
thumbnail: {instanceID: 0}
|
||||
order: 0
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f8a91ab8cce424696bd7b4cbbac110fe
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 90078c122328b4902a1e67255b30525a
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -11,7 +11,7 @@ PlayerSettings:
|
||||
defaultScreenOrientation: 4
|
||||
targetDevice: 2
|
||||
useOnDemandResources: 0
|
||||
accelerometerFrequency: 0
|
||||
accelerometerFrequency: 60
|
||||
companyName: Darkmatter Game Production
|
||||
productName: Kidsage Colorbook
|
||||
defaultCursor: {fileID: 0}
|
||||
@@ -148,7 +148,7 @@ PlayerSettings:
|
||||
loadStoreDebugModeEnabled: 0
|
||||
visionOSBundleVersion: 1.0
|
||||
tvOSBundleVersion: 1.0
|
||||
bundleVersion: 2.1
|
||||
bundleVersion: 1.9
|
||||
preloadedAssets:
|
||||
- {fileID: 11400000, guid: cc969dbecb228fa49b16da9273753a8f, type: 2}
|
||||
- {fileID: -944628639613478452, guid: 2bcd2660ca9b64942af0de543d8d7100, type: 3}
|
||||
@@ -180,7 +180,7 @@ PlayerSettings:
|
||||
iPhone: 1
|
||||
tvOS: 0
|
||||
overrideDefaultApplicationIdentifier: 1
|
||||
AndroidBundleVersionCode: 11
|
||||
AndroidBundleVersionCode: 9
|
||||
AndroidMinSdkVersion: 26
|
||||
AndroidTargetSdkVersion: 0
|
||||
AndroidPreferredInstallLocation: 1
|
||||
|
||||
Reference in New Issue
Block a user