Compare commits

...

28 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
Mausham
ecc2dd3dff reduced coloring objects 2026-07-07 15:52:58 +05:45
Savya Bikram Shah
e167af11fd Fixes optimization and UI/UX updates 2026-07-07 13:05:48 +05:45
Mausham
c2d3ff4dd9 Merge remote-tracking branch 'origin/savya' into work_branch
# Conflicts:
#	Assets/Darkmatter/Content/Fonts/static/Fredoka-SemiBold SDF.asset
2026-07-06 18:14:44 +05:45
Savya Bikram Shah
dfd76d187f video and difficulty based sorting fixes 2026-07-06 14:37:59 +05:45
Savya Bikram Shah
7b90efca98 Version changed 2026-07-06 14:24:13 +05:45
Savya Bikram Shah
e0a7fa9735 Events edits 2026-07-06 14:17:50 +05:45
Savya Bikram Shah
3df3d923fb Difficulty based sorting and analytics fixes 2026-07-06 13:20:27 +05:45
Mausham
40ba0df92d fixed pagination 2026-06-26 18:38:40 +05:45
Savya Bikram Shah
b28e1f637d Crash fixes 2026-06-26 18:18:49 +05:45
Savya Bikram Shah
21e5206626 Merge branch 'work_branch' of https://git.darkmatterproduction.org/savya/Colorbook into savya 2026-06-26 17:54:41 +05:45
Mausham
3095469c4f added diyo animation 2026-06-26 17:54:17 +05:45
Savya Bikram Shah
fc291cf116 Merge branch 'work_branch' of https://git.darkmatterproduction.org/savya/Colorbook into savya
# Conflicts:
#	Assets/Darkmatter/Content/Fonts/static/Fredoka-SemiBold SDF.asset
2026-06-26 16:19:08 +05:45
Savya Bikram Shah
7446e49c8f zooming for easy coloring 2026-06-26 16:18:39 +05:45
Mausham
f3c80be3ca fix 2026-06-26 15:37:52 +05:45
Mausham
db3354c88b added animations 2026-06-26 13:43:42 +05:45
Mausham
c64020628b added 40 drawing and coloring 2026-06-25 18:15:44 +05:45
Mausham
789edf1fba Merge remote-tracking branch 'origin/savya' into work_branch 2026-06-25 11:24:03 +05:45
Mausham
b9c9889145 fix 2026-06-25 11:23:34 +05:45
Savya Bikram Shah
608656bb83 Done 2026-06-25 11:23:06 +05:45
Savya Bikram Shah
ab706059c1 Minor work around 2026-06-25 11:19:01 +05:45
Mausham
93fafae63b added some drawing images yet remainded to create prefab
\
2026-06-23 18:41:23 +05:45
Mausham
a04a826ae0 added eagle coloring missing part 2026-06-23 17:39:38 +05:45
1352 changed files with 106088 additions and 28382 deletions

BIN
.DS_Store vendored

Binary file not shown.

BIN
Assets/.DS_Store vendored

Binary file not shown.

BIN
Assets/AddressableAssetsData/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -15,7 +15,8 @@ MonoBehaviour:
m_DefaultGroup: 0e030d5498bfe4ffd8443c796618c539 m_DefaultGroup: 0e030d5498bfe4ffd8443c796618c539
m_currentHash: m_currentHash:
serializedVersion: 2 serializedVersion: 2
Hash: 00000000000000000000000000000000 Hash: 9c85511c3b292d6ea9a478a45ca9832c
m_ExtractTypeTreeData: 0
m_OptimizeCatalogSize: 0 m_OptimizeCatalogSize: 0
m_BuildRemoteCatalog: 0 m_BuildRemoteCatalog: 0
m_CatalogRequestsTimeout: 0 m_CatalogRequestsTimeout: 0

View File

@@ -21,12 +21,24 @@ MonoBehaviour:
m_SerializedLabels: m_SerializedLabels:
- drawing - drawing
FlaggedDuringContentUpdateRestriction: 0 FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 14027365d88f82745bd4019ac24a4a48
m_Address: Rooster
m_ReadOnly: 0
m_SerializedLabels:
- drawing
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 14477b5d35d0be9439ecb935f1c4e64e - m_GUID: 14477b5d35d0be9439ecb935f1c4e64e
m_Address: Donut m_Address: Donut
m_ReadOnly: 0 m_ReadOnly: 0
m_SerializedLabels: m_SerializedLabels:
- drawing - drawing
FlaggedDuringContentUpdateRestriction: 0 FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 18367bdefdff1504fb125f7abf8d1283
m_Address: Motorcycle
m_ReadOnly: 0
m_SerializedLabels:
- drawing
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 2043f692673a79543afed5cb879f0e04 - m_GUID: 2043f692673a79543afed5cb879f0e04
m_Address: Five m_Address: Five
m_ReadOnly: 0 m_ReadOnly: 0
@@ -39,18 +51,48 @@ MonoBehaviour:
m_SerializedLabels: m_SerializedLabels:
- drawing - drawing
FlaggedDuringContentUpdateRestriction: 0 FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 2d13a177a6749834288f5e88a973fee7
m_Address: Yak
m_ReadOnly: 0
m_SerializedLabels:
- drawing
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 2d55441a1a6751c45bb9af623301426e - m_GUID: 2d55441a1a6751c45bb9af623301426e
m_Address: Car m_Address: Car
m_ReadOnly: 0 m_ReadOnly: 0
m_SerializedLabels: m_SerializedLabels:
- drawing - drawing
FlaggedDuringContentUpdateRestriction: 0 FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 2ee6ed56b9746934386767eca8c7a128
m_Address: Pankha
m_ReadOnly: 0
m_SerializedLabels:
- drawing
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 364f644d962504fa7ac2f382800014ea - m_GUID: 364f644d962504fa7ac2f382800014ea
m_Address: apple m_Address: apple
m_ReadOnly: 0 m_ReadOnly: 0
m_SerializedLabels: m_SerializedLabels:
- drawing - drawing
FlaggedDuringContentUpdateRestriction: 0 FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 37724d82877cdd742b46da6b173015e0
m_Address: Tipper
m_ReadOnly: 0
m_SerializedLabels:
- drawing
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 3dc99c70dccf80940bb59c8744ad6893
m_Address: Butterfly
m_ReadOnly: 0
m_SerializedLabels:
- drawing
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 3f2894c3e52b9034fb38c2d61987e19c
m_Address: Temple
m_ReadOnly: 0
m_SerializedLabels:
- drawing
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 4474b93704045a740ba9b8114e8a5238 - m_GUID: 4474b93704045a740ba9b8114e8a5238
m_Address: Fish m_Address: Fish
m_ReadOnly: 0 m_ReadOnly: 0
@@ -63,24 +105,90 @@ MonoBehaviour:
m_SerializedLabels: m_SerializedLabels:
- drawing - drawing
FlaggedDuringContentUpdateRestriction: 0 FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 4986d20a529345f48a5c05dc039848b5
m_Address: Snow Leopard
m_ReadOnly: 0
m_SerializedLabels:
- drawing
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 55bd536848430a14290186903c3ccc79
m_Address: Ketli
m_ReadOnly: 0
m_SerializedLabels:
- drawing
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 5c0e5d19eecbfae43a61b7c6145357a9
m_Address: Dashain Ping
m_ReadOnly: 0
m_SerializedLabels:
- drawing
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 5d02c6b7028e54e479bf1b645b4fcf44
m_Address: Madal
m_ReadOnly: 0
m_SerializedLabels:
- drawing
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 5f3b8273533c8514cbc5f46cf86f90e8
m_Address: Diyo
m_ReadOnly: 0
m_SerializedLabels:
- drawing
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 62ae112e11b695a40b889d773a36f8bd - m_GUID: 62ae112e11b695a40b889d773a36f8bd
m_Address: Elephant m_Address: Elephant
m_ReadOnly: 0 m_ReadOnly: 0
m_SerializedLabels: m_SerializedLabels:
- drawing - drawing
FlaggedDuringContentUpdateRestriction: 0 FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 67fc4e3245778f34083cfec4de5572ab
m_Address: Laligurans
m_ReadOnly: 0
m_SerializedLabels:
- drawing
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 6b911e576e57c68468991e31fe634b37
m_Address: Eagle
m_ReadOnly: 0
m_SerializedLabels:
- drawing
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 6e36ba5d4763c694289c8ca75ee81449 - m_GUID: 6e36ba5d4763c694289c8ca75ee81449
m_Address: Bus m_Address: Bus
m_ReadOnly: 0 m_ReadOnly: 0
m_SerializedLabels: m_SerializedLabels:
- drawing - drawing
FlaggedDuringContentUpdateRestriction: 0 FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 88e19e9e182745c48825ef3852cf8f4c
m_Address: Umbrella
m_ReadOnly: 0
m_SerializedLabels:
- drawing
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 8cdc379bbe2529a419e5ce0eedcbb09e - m_GUID: 8cdc379bbe2529a419e5ce0eedcbb09e
m_Address: Giraffe m_Address: Giraffe
m_ReadOnly: 0 m_ReadOnly: 0
m_SerializedLabels: m_SerializedLabels:
- drawing - drawing
FlaggedDuringContentUpdateRestriction: 0 FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 8efb0b102f9afc04da1b0fbfbcf1e764
m_Address: Doll
m_ReadOnly: 0
m_SerializedLabels:
- drawing
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 9274ef6670e95f04283827da968c8e8b
m_Address: Peacock
m_ReadOnly: 0
m_SerializedLabels:
- drawing
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 949b2f7f7192a4c40b11a6b2a88a4d96
m_Address: Chura
m_ReadOnly: 0
m_SerializedLabels:
- drawing
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 971be7b01d5eef24fa58094805ea92b9 - m_GUID: 971be7b01d5eef24fa58094805ea92b9
m_Address: Kite m_Address: Kite
m_ReadOnly: 0 m_ReadOnly: 0
@@ -93,12 +201,66 @@ MonoBehaviour:
m_SerializedLabels: m_SerializedLabels:
- drawing - drawing
FlaggedDuringContentUpdateRestriction: 0 FlaggedDuringContentUpdateRestriction: 0
- m_GUID: a0b17d65fcd1db642acd02e569d2ab18
m_Address: Tempo
m_ReadOnly: 0
m_SerializedLabels:
- drawing
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: a2ef043d1d7051c47a68e2ebce7bd3dd - m_GUID: a2ef043d1d7051c47a68e2ebce7bd3dd
m_Address: Dog m_Address: Dog
m_ReadOnly: 0 m_ReadOnly: 0
m_SerializedLabels: m_SerializedLabels:
- drawing - drawing
FlaggedDuringContentUpdateRestriction: 0 FlaggedDuringContentUpdateRestriction: 0
- m_GUID: a3267b093c298c146b47cbc2dec66201
m_Address: Crocodile
m_ReadOnly: 0
m_SerializedLabels:
- drawing
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: a3e107832ef0cd044b55484d4d084b2e
m_Address: Rhino
m_ReadOnly: 0
m_SerializedLabels:
- drawing
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: a80d3a0f58b23a04fa7e256dcbde84ea
m_Address: Mirga
m_ReadOnly: 0
m_SerializedLabels:
- drawing
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: b25eb4fe03b3fa44cba09f957cb66f0f
m_Address: Ping
m_ReadOnly: 0
m_SerializedLabels:
- drawing
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: b900a31d7a581b44880c1c1c0fd2120b
m_Address: Khukuri
m_ReadOnly: 0
m_SerializedLabels:
- drawing
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: b91952879cc013c4a937230bcfc50a13
m_Address: Monkey
m_ReadOnly: 0
m_SerializedLabels:
- drawing
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: bfafeadd98627734cabbb911a519be0e
m_Address: Mandala
m_ReadOnly: 0
m_SerializedLabels:
- drawing
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: d9cb166d2b3929646a141aa69bafabf3
m_Address: danfe
m_ReadOnly: 0
m_SerializedLabels:
- drawing
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: dc24a6d2802de5d4298d1da3d1415e97 - m_GUID: dc24a6d2802de5d4298d1da3d1415e97
m_Address: Envelop m_Address: Envelop
m_ReadOnly: 0 m_ReadOnly: 0
@@ -111,12 +273,60 @@ MonoBehaviour:
m_SerializedLabels: m_SerializedLabels:
- drawing - drawing
FlaggedDuringContentUpdateRestriction: 0 FlaggedDuringContentUpdateRestriction: 0
- m_GUID: e01d5e1bee4eb7d4b897466911d9ba5d
m_Address: Mala
m_ReadOnly: 0
m_SerializedLabels:
- drawing
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: e4fa512c35aded6499b818ff5844fa65
m_Address: Gagri
m_ReadOnly: 0
m_SerializedLabels:
- drawing
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: ee932fc542864974595b8f1114a85f06
m_Address: Bag
m_ReadOnly: 0
m_SerializedLabels:
- drawing
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: ef139978c562d1e49a73714f90943327
m_Address: Parrot
m_ReadOnly: 0
m_SerializedLabels:
- drawing
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: f3200c6715fea4a41bc71c0314b519cd - m_GUID: f3200c6715fea4a41bc71c0314b519cd
m_Address: Frog m_Address: Frog
m_ReadOnly: 0 m_ReadOnly: 0
m_SerializedLabels: m_SerializedLabels:
- drawing - drawing
FlaggedDuringContentUpdateRestriction: 0 FlaggedDuringContentUpdateRestriction: 0
- m_GUID: f64d814175be99b409d6b1c94e4b424c
m_Address: Tractor
m_ReadOnly: 0
m_SerializedLabels:
- drawing
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: f87bb07e8452a5b4c9ad8dc140bc8e73
m_Address: Cat
m_ReadOnly: 0
m_SerializedLabels:
- drawing
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: f8a1809740bda3247a55aee5355ccebf
m_Address: Tiger
m_ReadOnly: 0
m_SerializedLabels:
- drawing
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: fb4d005ac1c2506438718430fb29cfb2
m_Address: Rabbit
m_ReadOnly: 0
m_SerializedLabels:
- drawing
FlaggedDuringContentUpdateRestriction: 0
m_ReadOnly: 0 m_ReadOnly: 0
m_Settings: {fileID: 11400000, guid: 4a94ef317c3674edd8270e4ed15031f6, type: 2} m_Settings: {fileID: 11400000, guid: 4a94ef317c3674edd8270e4ed15031f6, type: 2}
m_SchemaSet: m_SchemaSet:

BIN
Assets/AppsFlyer/.DS_Store vendored Normal file

Binary file not shown.

BIN
Assets/Confetti FX/.DS_Store vendored Normal file

Binary file not shown.

Binary file not shown.

View File

@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
using Cysharp.Threading.Tasks; using Cysharp.Threading.Tasks;
@@ -20,6 +21,7 @@ public interface IColoringController
bool IsPlayingCompletionAnimation { get; } bool IsPlayingCompletionAnimation { get; }
bool HasNonAuthoredColors { get; } bool HasNonAuthoredColors { get; }
IDisposable UseNonZoomedView();
void ResetAll(); void ResetAll();
void Clear(); void Clear();
} }

View File

@@ -9,10 +9,12 @@ namespace Darkmatter.Core.Contracts.Features.DrawingCatalog
{ {
string Id { get; } string Id { get; }
string DisplayName { get; } string DisplayName { get; }
int Difficulty { get; }
Sprite DefaultThumbnail { get; } Sprite DefaultThumbnail { get; }
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

@@ -6,9 +6,12 @@ namespace Darkmatter.Core.Contracts.Features.DrawingCatalog
{ {
public interface IDrawingTemplateCatalog public interface IDrawingTemplateCatalog
{ {
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

@@ -11,9 +11,9 @@ namespace Darkmatter.Core.Contracts.Services.Analytics
// Onboarding / activation funnel // Onboarding / activation funnel
public const string IntroStarted = "intro_started"; public const string IntroStarted = "intro_started";
public const string IntroCompleted = "intro_completed"; public const string IntroCompleted = "intro_completed";
public const string TutorialStarted = "tutorial_started"; public const string TutorialStarted = "tutorial_begin";
public const string TutorialStepCompleted = "tutorial_step_completed"; public const string TutorialStepCompleted = "tutorial_step_completed";
public const string TutorialCompleted = "tutorial_completed"; public const string TutorialCompleted = "tutorial_complete";
public const string PlayClicked = "play_clicked"; public const string PlayClicked = "play_clicked";
public const string FirstDrawingStarted = "first_drawing_started"; 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 ShapeAssembled = "shape_assembled";
public const string ColorApplied = "color_applied"; public const string ColorApplied = "color_applied";
// Progression & content (GA4 recommended level_start / level_complete) // Progression & content. level_start and level_end are GA4-recommended games events.
public const string LevelStart = "level_start"; public const string LevelStart = "level_start";
public const string LevelComplete = "level_complete"; public const string LevelComplete = "level_end";
public const string AllContentCompleted = "all_content_completed"; public const string AllContentCompleted = "all_content_completed";
// Gallery capture (pre-existing) // Gallery capture (pre-existing)

View File

@@ -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 /// 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 /// 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. /// be registered as a Custom Dimension in the Firebase console. GA4 limit: 25 params per event.
/// GA4-recommended events (level_start/level_complete/ad_impression) reuse GA4's reserved param names /// GA4-recommended events (level_start/level_end/ad_impression) reuse GA4's reserved param names
/// (level_name, success, value, currency, ad_platform, ad_format, ad_unit_name) so they're recognised. /// (level_name, success, value, currency, ad_platform, ad_format, ad_unit_name) so they're recognised.
/// </summary> /// </summary>
public static class AnalyticsParams public static class AnalyticsParams
{ {
// Content identity // Content identity
public const string DrawingId = "drawing_id"; public const string DrawingId = "drawing_id";
public const string LevelName = "level_name"; // GA4 reserved (level_start/level_complete) public const string LevelName = "level_name"; // GA4 reserved (level_start/level_end)
// Tutorial // Tutorial
public const string StepId = "step_id"; public const string StepId = "step_id";
@@ -23,7 +23,7 @@ namespace Darkmatter.Core.Contracts.Services.Analytics
public const string Attempts = "attempts"; public const string Attempts = "attempts";
public const string ApplyIndex = "apply_index"; public const string ApplyIndex = "apply_index";
public const string CompletionCount = "completion_count"; public const string CompletionCount = "completion_count";
public const string Success = "success"; // GA4 reserved (level_complete) public const string Success = "success"; // GA4 reserved (level_end)
// Monetization (GA4 ad_impression reserved names) // Monetization (GA4 ad_impression reserved names)
public const string Value = "value"; public const string Value = "value";

View File

@@ -1,3 +1,4 @@
using System;
using System.Threading; using System.Threading;
using Cysharp.Threading.Tasks; using Cysharp.Threading.Tasks;
using UnityEngine; using UnityEngine;
@@ -7,6 +8,6 @@ namespace Darkmatter.Core.Contracts.Services.Capture
public interface ICaptureService public interface ICaptureService
{ {
UniTask<byte[]> CapturePngAsync(GameObject captureObject, float scale, UniTask<byte[]> CapturePngAsync(GameObject captureObject, float scale,
CancellationToken cancellationToken = default); Func<IDisposable> captureViewScopeFactory = null, CancellationToken cancellationToken = default);
} }
} }

View File

@@ -1,4 +1,8 @@
namespace Darkmatter.Core.Data.Signals.Features.Capture namespace Darkmatter.Core.Data.Signals.Features.Capture
{ {
public record struct GallerySaveCompletedSignal(bool Success); /// <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);
} }

View File

@@ -0,0 +1,10 @@
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;
}

View File

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

View File

@@ -12,20 +12,29 @@ namespace Darkmatter.Core.Data.Static.Features.DrawingTemplate
{ {
[SerializeField] private string id; [SerializeField] private string id;
[SerializeField] private string displayName; [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 Sprite defaultThumbnail;
[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";
public string Id => id; public string Id => id;
public string DisplayName => displayName; 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 Sprite DefaultThumbnail => defaultThumbnail;
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;
@@ -34,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)
{ {
@@ -44,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

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

View File

@@ -0,0 +1,78 @@
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace Darkmatter.EditorTools
{
public static class AddSelectedPrefabToPaperRoot
{
[MenuItem("Tools/Colorbook/Add Selected Prefab To PaperRoot")]
private static void AddSelected()
{
var prefab = Selection.activeObject as GameObject;
if (prefab == null || PrefabUtility.GetPrefabAssetType(prefab) == PrefabAssetType.NotAPrefab)
{
EditorUtility.DisplayDialog(
"Select a prefab",
"Select a drawing, coloring, or animation prefab in the Project window first.",
"OK");
return;
}
var paperRoot = FindPaperRoot();
if (paperRoot == null)
{
EditorUtility.DisplayDialog(
"PaperRoot not found",
"Open the Gameplay scene and make sure it contains an object named PaperRoot.",
"OK");
return;
}
var instance = PrefabUtility.InstantiatePrefab(prefab, paperRoot.scene) as GameObject;
if (instance == null) return;
Undo.RegisterCreatedObjectUndo(instance, $"Add {prefab.name} to PaperRoot");
Undo.SetTransformParent(instance.transform, paperRoot.transform, "Parent prefab to PaperRoot");
if (instance.transform is RectTransform rectTransform)
{
rectTransform.anchorMin = Vector2.zero;
rectTransform.anchorMax = Vector2.one;
rectTransform.anchoredPosition = Vector2.zero;
rectTransform.sizeDelta = Vector2.zero;
rectTransform.localScale = Vector3.one;
}
else
{
instance.transform.localPosition = Vector3.zero;
instance.transform.localRotation = Quaternion.identity;
instance.transform.localScale = Vector3.one;
}
Selection.activeGameObject = instance;
EditorSceneManager.MarkSceneDirty(paperRoot.scene);
}
[MenuItem("Tools/Colorbook/Add Selected Prefab To PaperRoot", true)]
private static bool ValidateAddSelected()
{
return !EditorApplication.isPlayingOrWillChangePlaymode;
}
private static GameObject FindPaperRoot()
{
var activeScene = SceneManager.GetActiveScene();
foreach (var root in activeScene.GetRootGameObjects())
{
var transforms = root.GetComponentsInChildren<Transform>(true);
foreach (var candidate in transforms)
if (candidate.name == "PaperRoot")
return candidate.gameObject;
}
return null;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 675d955c682824cd9904fb2e96517df1

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

@@ -1,3 +1,4 @@
using System;
using System.Threading; using System.Threading;
using Cysharp.Threading.Tasks; using Cysharp.Threading.Tasks;
using Darkmatter.Core.Contracts.Features.Progression; using Darkmatter.Core.Contracts.Features.Progression;
@@ -14,6 +15,10 @@ namespace Darkmatter.Features.AppBoot.Flow
{ {
public class AppBootFlow : IAsyncStartable 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 AppBootSceneRefs _sceneRefs;
private readonly ISceneService _sceneService; private readonly ISceneService _sceneService;
private readonly IEventBus _eventBus; private readonly IEventBus _eventBus;
@@ -30,36 +35,96 @@ namespace Darkmatter.Features.AppBoot.Flow
public async UniTask StartAsync(CancellationToken cancellation = default) 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 #if UNITY_ANDROID || UNITY_IOS
Application.targetFrameRate = 60; Application.targetFrameRate = 60;
#endif #endif
Screen.sleepTimeout = SleepTimeout.NeverSleep; Screen.sleepTimeout = SleepTimeout.NeverSleep;
await _progression.LoadAsync(); await _progression.LoadAsync();
var tcs = new UniTaskCompletionSource();
var player = _sceneRefs.IntroVideoPlayer; 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 _sceneService.LoadSceneAsync(nameof(GameScene.MainMenu), null, cancellation);
await tcs.Task.AttachExternalCancellation(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;
player.Stop(); player.Stop();
var rt = player.targetTexture; var rt = player.targetTexture;
if (rt != null) rt.Release(); 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());
} }
} }
} }

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

@@ -1,7 +1,6 @@
using System; using System;
using UnityEngine;
namespace Darkmatter.Features.Artbook namespace Darkmatter.Features.Artbook
{ {
public record struct ArtbookEntry(string Id, string Name, Texture2D Thumbnail, DateTime UpdatedUtc); public record struct ArtbookEntry(string Id, string Name, DateTime UpdatedUtc);
} }

View File

@@ -7,6 +7,7 @@ using Darkmatter.Core.Contracts.Features.DrawingCatalog;
using Darkmatter.Core.Contracts.Features.Progression; using Darkmatter.Core.Contracts.Features.Progression;
using Darkmatter.Core.Contracts.Features.SaveGate; using Darkmatter.Core.Contracts.Features.SaveGate;
using Darkmatter.Core.Contracts.Services.Gallery; using Darkmatter.Core.Contracts.Services.Gallery;
using Darkmatter.Core.Data.Signals.Features.Capture;
using Darkmatter.Core.Data.Signals.Features.Drawing; using Darkmatter.Core.Data.Signals.Features.Drawing;
using Darkmatter.Libs.Observer; using Darkmatter.Libs.Observer;
using UnityEngine; using UnityEngine;
@@ -26,12 +27,13 @@ namespace Darkmatter.Features.Artbook
private readonly IRewardedSaveGate _saveGate; private readonly IRewardedSaveGate _saveGate;
private readonly List<ArtbookEntry> _entries = new(); private readonly List<ArtbookEntry> _entries = new();
private readonly List<Sprite> _ownedSprites = new(); private readonly List<Sprite> _visibleSprites = new();
private readonly List<Texture2D> _ownedTextures = new(); private readonly List<Texture2D> _visibleTextures = new();
private Action _onClose; private Action _onClose;
private CancellationTokenSource _cts; private CancellationTokenSource _cts;
private IDisposable _openSubscription; private IDisposable _openSubscription;
private int _currentSpread; private int _currentSpread;
private int _renderVersion;
public ArtbookPresenter( public ArtbookPresenter(
ArtbookView view, ArtbookView view,
@@ -75,33 +77,40 @@ namespace Darkmatter.Features.Artbook
private async UniTaskVoid LoadAndShowAsync(CancellationToken ct) private async UniTaskVoid LoadAndShowAsync(CancellationToken ct)
{ {
ClearOwnedAssets(); try
_entries.Clear();
_currentSpread = 0;
await _catalog.FetchAsync();
if (ct.IsCancellationRequested) return;
foreach (var id in _catalog.AllTemplateIds)
{ {
ClearVisibleAssets();
_entries.Clear();
_currentSpread = 0;
await _catalog.FetchAsync();
if (ct.IsCancellationRequested) return; if (ct.IsCancellationRequested) return;
var progress = _progression.GetProgress(id); foreach (var id in _catalog.AllTemplateIds)
if (progress is not { hasThumbnail: true }) continue; {
if (ct.IsCancellationRequested) return;
var texture = await _progression.GetCachedThumbnailAsync(id); var progress = _progression.GetProgress(id);
if (ct.IsCancellationRequested) return; if (progress is not { hasThumbnail: true }) continue;
if (texture == null) continue;
var template = await _catalog.LoadAsync(id); var template = await _catalog.LoadAsync(id);
if (ct.IsCancellationRequested) return; if (ct.IsCancellationRequested) return;
if (template == null) continue;
_ownedTextures.Add(texture); _entries.Add(new ArtbookEntry(id, template.DisplayName, progress.Value.UpdatedUtc));
_entries.Add(new ArtbookEntry(id, template.DisplayName, texture, progress.Value.UpdatedUtc)); }
_entries.Sort((a, b) => b.UpdatedUtc.CompareTo(a.UpdatedUtc));
RenderSpread();
}
catch (OperationCanceledException)
{
// Opening/closing the art book can cancel the load at any await point.
}
catch (Exception e)
{
Debug.LogError($"[Artbook] Failed to load entries: {e}");
} }
_entries.Sort((a, b) => b.UpdatedUtc.CompareTo(a.UpdatedUtc));
RenderSpread();
} }
private void HandlePrevSpreadClicked() private void HandlePrevSpreadClicked()
@@ -122,7 +131,7 @@ namespace Darkmatter.Features.Artbook
private void RenderSpread() private void RenderSpread()
{ {
_view.SetSpread(GetLeftEntry(), SpriteFor(GetLeftEntry()), GetRightEntry(), SpriteFor(GetRightEntry())); RenderSpreadAsync(++_renderVersion, _cts?.Token ?? CancellationToken.None).Forget();
_view.SetNavigation(_currentSpread > 0, _currentSpread < TotalSpreads - 1); _view.SetNavigation(_currentSpread > 0, _currentSpread < TotalSpreads - 1);
} }
@@ -138,13 +147,51 @@ namespace Darkmatter.Features.Artbook
return idx < _entries.Count ? _entries[idx] : null; return idx < _entries.Count ? _entries[idx] : null;
} }
private Sprite SpriteFor(ArtbookEntry? entry) private async UniTaskVoid RenderSpreadAsync(int version, CancellationToken ct)
{ {
if (!entry.HasValue) return null; var left = GetLeftEntry();
var tex = entry.Value.Thumbnail; var right = GetRightEntry();
ClearVisibleAssets();
_view.SetSpread(left, null, right, null);
try
{
var leftSprite = await SpriteForAsync(left, version, ct);
var rightSprite = await SpriteForAsync(right, version, ct);
if (ct.IsCancellationRequested || version != _renderVersion)
{
return;
}
_view.SetSpread(left, leftSprite, right, rightSprite);
}
catch (OperationCanceledException)
{
if (version == _renderVersion) ClearVisibleAssets();
}
catch (Exception e)
{
if (version == _renderVersion) ClearVisibleAssets();
Debug.LogError($"[Artbook] Failed to render spread: {e}");
}
}
private async UniTask<Sprite> SpriteForAsync(ArtbookEntry? entry, int version, CancellationToken ct)
{
if (!entry.HasValue || ct.IsCancellationRequested || version != _renderVersion) return null;
var tex = await _progression.GetCachedThumbnailAsync(entry.Value.Id);
if (ct.IsCancellationRequested || version != _renderVersion)
{
if (tex != null) UnityEngine.Object.Destroy(tex);
return null;
}
if (tex == null) return null; if (tex == null) return null;
var sprite = Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), new Vector2(0.5f, 0.5f), 100f); var sprite = Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), new Vector2(0.5f, 0.5f), 100f);
_ownedSprites.Add(sprite); _visibleTextures.Add(tex);
_visibleSprites.Add(sprite);
return sprite; return sprite;
} }
@@ -153,13 +200,45 @@ namespace Darkmatter.Features.Artbook
private async UniTaskVoid SaveToGalleryAsync(ArtbookEntry? entry) private async UniTaskVoid SaveToGalleryAsync(ArtbookEntry? entry)
{ {
if (!entry.HasValue || entry.Value.Thumbnail == null) return; Texture2D texture = null;
var ct = _cts?.Token ?? CancellationToken.None; try
// Same kid-friendly prompt + rewarded ad as the gameplay save button. {
if (!await _saveGate.RequestSaveAsync(ct)) return; if (!entry.HasValue) return;
await _gallery.SaveImageAsync(entry.Value.Thumbnail, entry.Value.Name, ct); var ct = _cts?.Token ?? CancellationToken.None;
// The art book has no success popup of its own, so use the shared toast. // Same kid-friendly prompt + rewarded ad as the gameplay save button.
await _saveGate.ShowSavedAsync(ct); if (!await _saveGate.RequestSaveAsync(ct)) return;
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));
}
// The art book has no success popup of its own, so use the shared toast.
await _saveGate.ShowSavedAsync(ct);
}
catch (OperationCanceledException)
{
// The art book was closed or replaced while saving.
}
catch (Exception e)
{
Debug.LogError($"[Artbook] Failed to save page: {e}");
}
finally
{
if (texture != null) UnityEngine.Object.Destroy(texture);
}
} }
private void HandleLeftEditClicked() => OpenForEdit(GetLeftEntry()); private void HandleLeftEditClicked() => OpenForEdit(GetLeftEntry());
@@ -169,27 +248,43 @@ namespace Darkmatter.Features.Artbook
{ {
if (!entry.HasValue) return; if (!entry.HasValue) return;
_eventBus.Publish(new DrawingSelectedSignal(entry.Value.Id)); _eventBus.Publish(new DrawingSelectedSignal(entry.Value.Id));
StopActiveWork();
_view.Hide(); _view.Hide();
} }
private void HandleBackButtonClicked() private void HandleBackButtonClicked()
{ {
_onClose?.Invoke(); _onClose?.Invoke();
StopActiveWork();
_view.Hide(); _view.Hide();
} }
private void HandleColorbookButtonClicked() private void HandleColorbookButtonClicked()
{ {
_eventBus.Publish(new OpenColorBookSignal()); _eventBus.Publish(new OpenColorBookSignal());
StopActiveWork();
_view.Hide(); _view.Hide();
} }
private void StopActiveWork()
{
_renderVersion++;
_cts?.Cancel();
_view.SetSpread(null, null, null, null);
ClearVisibleAssets();
}
private void ClearOwnedAssets() private void ClearOwnedAssets()
{ {
foreach (var s in _ownedSprites) UnityEngine.Object.Destroy(s); ClearVisibleAssets();
foreach (var t in _ownedTextures) UnityEngine.Object.Destroy(t); }
_ownedSprites.Clear();
_ownedTextures.Clear(); private void ClearVisibleAssets()
{
foreach (var s in _visibleSprites) UnityEngine.Object.Destroy(s);
foreach (var t in _visibleTextures) UnityEngine.Object.Destroy(t);
_visibleSprites.Clear();
_visibleTextures.Clear();
} }
public void Dispose() public void Dispose()
@@ -208,4 +303,4 @@ namespace Darkmatter.Features.Artbook
ClearOwnedAssets(); ClearOwnedAssets();
} }
} }
} }

View File

@@ -51,17 +51,23 @@ namespace Darkmatter.Features.Capture
// A null result keeps the existing thumbnail and skips the gallery write (both no-op on null). // A null result keeps the existing thumbnail and skips the gallery write (both no-op on null).
if (_coloring.IsPlayingCompletionAnimation) return null; if (_coloring.IsPlayingCompletionAnimation) return null;
var png = await _captureService.CapturePngAsync(_refs.PaperRoot.gameObject, _config.CaptureScale, ct); var png = await _captureService.CapturePngAsync(
_refs.PaperRoot.gameObject,
_config.CaptureScale,
_coloring.UseNonZoomedView,
ct);
if (!saveToGallery || png == null || png.Length == 0) return png; if (!saveToGallery || png == null || png.Length == 0) return png;
var tex = new Texture2D(2, 2, TextureFormat.RGBA32, mipChain: false); var tex = new Texture2D(2, 2, TextureFormat.RGBA32, mipChain: false);
var success = 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 try
{ {
if (tex.LoadImage(png)) if (tex.LoadImage(png))
{ {
var fileName = await BuildFileNameAsync(); var fileName = await BuildFileNameAsync();
_bus.Publish(new GallerySaveStartedSignal());
await _galleryService.SaveImageAsync(tex, fileName, ct); await _galleryService.SaveImageAsync(tex, fileName, ct);
success = true; success = true;
} }
@@ -69,7 +75,7 @@ namespace Darkmatter.Features.Capture
finally finally
{ {
UnityEngine.Object.Destroy(tex); UnityEngine.Object.Destroy(tex);
_bus.Publish(new GallerySaveCompletedSignal(success)); _bus.Publish(new GallerySaveCompletedSignal(success, _progression.LastOpenedTemplateId));
} }
return png; return png;
} }

View File

@@ -68,7 +68,13 @@ public class ColorbookFlowController : IAsyncStartable, IDisposable
// one VContainer cancels on teardown, and the linked _scopeCts may lag the callback order. // one VContainer cancels on teardown, and the linked _scopeCts may lag the callback order.
if (cancellation.IsCancellationRequested || ct.IsCancellationRequested) return; if (cancellation.IsCancellationRequested || ct.IsCancellationRequested) return;
if (!_navigatingToGameplay) _loadingScreen.Hide(); 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());
}
PrewarmInterstitialAdAsync().Forget(); PrewarmInterstitialAdAsync().Forget();
} }

View File

@@ -8,7 +8,8 @@
"GUID:b4c9f7fbf1e144933a1797dc208ece5f", "GUID:b4c9f7fbf1e144933a1797dc208ece5f",
"GUID:b0214a6008ed146ff8f122a6a9c2f6cc", "GUID:b0214a6008ed146ff8f122a6a9c2f6cc",
"GUID:f51ebe6a0ceec4240a699833d6309b23", "GUID:f51ebe6a0ceec4240a699833d6309b23",
"GUID:80ecb87cae9c44d19824e70ea7229748" "GUID:80ecb87cae9c44d19824e70ea7229748",
"GUID:75469ad4d38634e559750d17036d5f7c"
], ],
"includePlatforms": [], "includePlatforms": [],
"excludePlatforms": [], "excludePlatforms": [],
@@ -19,4 +20,4 @@
"defineConstraints": [], "defineConstraints": [],
"versionDefines": [], "versionDefines": [],
"noEngineReferences": false "noEngineReferences": false
} }

View File

@@ -11,6 +11,7 @@ namespace Darkmatter.Features.Coloring
public class ColoringFeatureModule : MonoBehaviour, IModule public class ColoringFeatureModule : MonoBehaviour, IModule
{ {
[SerializeField] private ColorPaletteHolderView paletteHolderView; [SerializeField] private ColorPaletteHolderView paletteHolderView;
[SerializeField] private ColoringPinchZoomController pinchZoomController;
public void Register(IContainerBuilder builder) public void Register(IContainerBuilder builder)
{ {
@@ -20,6 +21,7 @@ namespace Darkmatter.Features.Coloring
builder.RegisterEntryPoint<ColorPaletteHolderPresenter>().WithParameter(paletteHolderView); builder.RegisterEntryPoint<ColorPaletteHolderPresenter>().WithParameter(paletteHolderView);
} }
builder.RegisterComponent(pinchZoomController);
builder.Register<IColorButtonFactory, ColorButtonFactory>(Lifetime.Singleton); builder.Register<IColorButtonFactory, ColorButtonFactory>(Lifetime.Singleton);
builder.Register<ColoringStateRepository>(Lifetime.Singleton); builder.Register<ColoringStateRepository>(Lifetime.Singleton);
builder.Register<IColoringController, ColoringController>(Lifetime.Singleton); builder.Register<IColoringController, ColoringController>(Lifetime.Singleton);

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,15 @@ 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 bool _isPlayingCompletionAnimation; private bool _isPlayingCompletionAnimation;
private readonly List<ColorRegionView> _regions = new(); private readonly List<ColorRegionView> _regions = new();
private readonly List<ColorButton> _buttons = new(); private readonly List<ColorButton> _buttons = new();
@@ -41,26 +48,30 @@ public class ColoringController : IColoringController, IDisposable
public ColoringController( public ColoringController(
ColoringStateRepository repository, ColoringStateRepository repository,
IColorButtonFactory buttonFactory, IColorButtonFactory buttonFactory,
ColoringPinchZoomController pinchZoom,
IEventBus bus, IEventBus bus,
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;
_bus = bus; _bus = bus;
_assetProviderService = assetProviderService; _assetProviderService = assetProviderService;
_history = history; _history = history;
_pinchZoom = pinchZoom;
_refs = refs; _refs = refs;
_paletteHolder = paletteHolder; _paletteHolder = paletteHolder;
_audioService = audioService;
} }
public async UniTask InitializeRegionsAsync(IDrawingTemplate template, public async UniTask InitializeRegionsAsync(IDrawingTemplate template,
IReadOnlyDictionary<string, Color> savedColors, CancellationToken ct) IReadOnlyDictionary<string, Color> savedColors, CancellationToken ct)
{ {
Clear(); Clear();
_paletteHolder.Show(); _ = _paletteHolder.Show();
await TryLoadColorButtonPrefabAsync(ct); await TryLoadColorButtonPrefabAsync(ct);
ct.ThrowIfCancellationRequested(); ct.ThrowIfCancellationRequested();
await TryLoadColorPaletteAsync(template, ct); await TryLoadColorPaletteAsync(template, ct);
@@ -110,8 +121,11 @@ public class ColoringController : IColoringController, IDisposable
{ {
if (_completionAnimationInstance == null || _completionAnimationView == null) return; if (_completionAnimationInstance == null || _completionAnimationView == null) return;
if (_colorInstance != null) _colorInstance.SetActive(false); if (_colorInstance != null) _colorInstance.SetActive(false);
if (_pinchZoom != null)
_pinchZoom.ResetZoom();
_completionAnimationInstance.SetActive(true); _completionAnimationInstance.SetActive(true);
_isPlayingCompletionAnimation = true; _isPlayingCompletionAnimation = true;
PlayCompletionSound();
try try
{ {
await _completionAnimationView.PlayAsync(ct); await _completionAnimationView.PlayAsync(ct);
@@ -124,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
@@ -138,6 +169,14 @@ public class ColoringController : IColoringController, IDisposable
} }
} }
public IDisposable UseNonZoomedView()
{
if (_pinchZoom == null)
return null;
return _pinchZoom.UseNonZoomedView();
}
public void ResetAll() public void ResetAll()
{ {
if (_regions.Count == 0) return; if (_regions.Count == 0) return;
@@ -154,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)
@@ -169,12 +209,16 @@ public class ColoringController : IColoringController, IDisposable
_completionAnimationInstance = null; _completionAnimationInstance = null;
_completionAnimationView = null; _completionAnimationView = null;
} }
_completionSound = null;
if (_colorInstance != null) if (_colorInstance != null)
{ {
UnityEngine.Object.Destroy(_colorInstance); UnityEngine.Object.Destroy(_colorInstance);
_colorInstance = null; _colorInstance = null;
} }
if (_pinchZoom != null)
_pinchZoom.SetTarget(null);
} }
public void Dispose() => Clear(); public void Dispose() => Clear();
@@ -182,12 +226,14 @@ public class ColoringController : IColoringController, IDisposable
private void InitializeColorRegions(IDrawingTemplate template, IReadOnlyDictionary<string, Color> savedColors) private void InitializeColorRegions(IDrawingTemplate template, IReadOnlyDictionary<string, Color> savedColors)
{ {
_colorInstance = UnityEngine.Object.Instantiate(template.ColoringPrefab, _refs.PaperRoot); _colorInstance = UnityEngine.Object.Instantiate(template.ColoringPrefab, _refs.PaperRoot);
InitializePinchZoom(_refs.PaperRoot);
if (template.CompletionAnimationPrefab != null) if (template.CompletionAnimationPrefab != null)
{ {
_completionAnimationInstance = UnityEngine.Object.Instantiate(template.CompletionAnimationPrefab, _refs.PaperRoot); _completionAnimationInstance = UnityEngine.Object.Instantiate(template.CompletionAnimationPrefab, _refs.PaperRoot);
_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)
@@ -207,6 +253,17 @@ public class ColoringController : IColoringController, IDisposable
} }
} }
private void InitializePinchZoom(RectTransform target)
{
if (target == null)
{
return;
}
if (_pinchZoom != null)
_pinchZoom.SetTarget(target);
}
private async UniTask TryLoadColorButtonPrefabAsync(CancellationToken ct) private async UniTask TryLoadColorButtonPrefabAsync(CancellationToken ct)
{ {
if (_colorButtonPrefab != null) return; if (_colorButtonPrefab != null) return;
@@ -231,4 +288,4 @@ public class ColoringController : IColoringController, IDisposable
foreach (var color in _repository.SelectedPalette.Colors) foreach (var color in _repository.SelectedPalette.Colors)
_buttons.Add(_buttonFactory.Create(_colorButtonPrefab, color)); _buttons.Add(_buttonFactory.Create(_colorButtonPrefab, color));
} }
} }

View File

@@ -0,0 +1,454 @@
using System;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem;
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;
[SerializeField] private float scrollZoomSensitivity = 0.0015f;
private RectTransform _target;
private RectTransform _parent;
private Canvas _canvas;
private Camera _eventCamera;
private bool _isPinching;
private float _currentZoom = 1f;
private Vector3 _baseScale = Vector3.one;
private Vector2 _baseAnchoredPosition;
private PanDragRelay _panRelay;
public void SetTarget(RectTransform target)
{
ResetZoom();
_target = target;
_parent = target != null ? target.parent as RectTransform : null;
_canvas = target != null ? target.GetComponentInParent<Canvas>() : null;
_eventCamera = ResolveEventCamera(_canvas);
_baseScale = target != null ? target.localScale : Vector3.one;
_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()
{
if (_target != null)
{
_target.localScale = _baseScale;
_target.anchoredPosition = _baseAnchoredPosition;
}
_currentZoom = 1f;
_isPinching = false;
UpdatePanRelayState();
}
public IDisposable UseNonZoomedView()
{
if (_target == null)
return null;
var state = new ViewState(_target.localScale, _target.anchoredPosition, _currentZoom);
ResetZoom();
return new NonZoomedViewScope(this, state);
}
private void Awake()
{
enabled = false;
}
private void Update()
{
if (_target == null || _parent == null)
{
enabled = false;
return;
}
if (TryApplyPinchZoom())
{
return;
}
_isPinching = false;
TryApplyScrollZoom();
TryApplyMousePan();
}
private bool TryApplyPinchZoom()
{
if (!TryGetTouchSamples(out TouchSample firstTouch, out TouchSample secondTouch))
{
return false;
}
float distance = Vector2.Distance(firstTouch.Position, secondTouch.Position);
if (distance < MinimumPinchDistance)
{
return true;
}
Vector2 midpoint = (firstTouch.Position + secondTouch.Position) * 0.5f;
if (!TryGetParentLocalPoint(midpoint, out Vector2 midpointLocal))
{
return true;
}
if (!_isPinching)
{
_isPinching = true;
return true;
}
float previousDistance = Vector2.Distance(firstTouch.PreviousPosition, secondTouch.PreviousPosition);
if (previousDistance < MinimumPinchDistance)
{
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);
return true;
}
private void TryApplyScrollZoom()
{
Mouse mouse = Mouse.current;
if (mouse == null)
{
return;
}
float scroll = mouse.scroll.ReadValue().y;
if (Mathf.Approximately(scroll, 0f))
{
return;
}
Vector2 screenPosition = mouse.position.ReadValue();
if (!IsInputPointAllowed(screenPosition))
{
return;
}
if (!TryGetParentLocalPoint(screenPosition, out Vector2 localPoint))
{
return;
}
float targetZoom = Mathf.Clamp(_currentZoom * (1f + scroll * scrollZoomSensitivity), minZoom, maxZoom);
ZoomTowards(localPoint, targetZoom);
}
private void ZoomTowards(Vector2 parentLocalPoint, float targetZoom)
{
if (Mathf.Approximately(targetZoom, _currentZoom))
{
return;
}
ApplyZoomPan(parentLocalPoint, parentLocalPoint, targetZoom);
}
private void ApplyZoomPan(Vector2 previousLocalPoint, Vector2 currentLocalPoint, float targetZoom)
{
Vector2 contentPoint = (previousLocalPoint - _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));
}
private bool TryGetParentLocalPoint(Vector2 screenPoint, out Vector2 localPoint)
{
return RectTransformUtility.ScreenPointToLocalPointInRectangle(_parent, screenPoint, _eventCamera,
out localPoint);
}
private bool TryGetTouchSamples(out TouchSample firstTouch, out TouchSample secondTouch)
{
firstTouch = default;
secondTouch = default;
Touchscreen touchscreen = Touchscreen.current;
if (touchscreen == null)
{
return false;
}
int count = 0;
foreach (var touchControl in touchscreen.touches)
{
if (!touchControl.press.isPressed)
{
continue;
}
Vector2 position = touchControl.position.ReadValue();
if (!IsInputPointAllowed(position))
{
continue;
}
var sample = new TouchSample(position, touchControl.delta.ReadValue());
if (count == 0)
{
firstTouch = sample;
}
else
{
secondTouch = sample;
return true;
}
count++;
}
return false;
}
private bool IsInputPointAllowed(Vector2 screenPoint)
{
return _target != null &&
RectTransformUtility.RectangleContainsScreenPoint(_target, screenPoint, _eventCamera);
}
private static Camera ResolveEventCamera(Canvas canvas)
{
if (canvas == null || canvas.renderMode == RenderMode.ScreenSpaceOverlay)
{
return null;
}
return canvas.worldCamera;
}
private void RestoreView(ViewState state)
{
if (_target == null)
return;
_target.localScale = state.Scale;
_target.anchoredPosition = state.AnchoredPosition;
_currentZoom = state.CurrentZoom;
_isPinching = false;
UpdatePanRelayState();
}
private readonly struct ViewState
{
public readonly Vector3 Scale;
public readonly Vector2 AnchoredPosition;
public readonly float CurrentZoom;
public ViewState(Vector3 scale, Vector2 anchoredPosition, float currentZoom)
{
Scale = scale;
AnchoredPosition = anchoredPosition;
CurrentZoom = currentZoom;
}
}
private sealed class NonZoomedViewScope : IDisposable
{
private ColoringPinchZoomController _controller;
private readonly ViewState _state;
public NonZoomedViewScope(ColoringPinchZoomController controller, ViewState state)
{
_controller = controller;
_state = state;
}
public void Dispose()
{
if (_controller == null)
return;
if (_controller != null)
_controller.RestoreView(_state);
_controller = null;
}
}
private readonly struct TouchSample
{
public readonly Vector2 Position;
public readonly Vector2 PreviousPosition;
public readonly Vector2 Delta;
public TouchSample(Vector2 position, Vector2 delta)
{
Position = position;
PreviousPosition = position - 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

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ddf9e0ad8b4b4ee59470410f7cf40689
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -57,8 +57,8 @@ public sealed class DrawingCatalogController : IDrawingCatalogController
private void Refresh() private void Refresh()
{ {
_visible.Clear(); _visible.Clear();
// Catalog already orders ids by difficulty (easiest first), then id.
_visible.AddRange(_catalog.AllTemplateIds); _visible.AddRange(_catalog.AllTemplateIds);
_visible.Sort(StringComparer.OrdinalIgnoreCase);
ListChanged?.Invoke(); ListChanged?.Invoke();
} }
} }

View File

@@ -13,8 +13,8 @@ namespace Darkmatter.Features.DrawingCatalog
{ {
Id = id; Id = id;
thumbnail.sprite = thumbnailSprite; thumbnail.sprite = thumbnailSprite;
button.onClick.RemoveAllListeners();
button.onClick.AddListener(onClick); button.onClick.AddListener(onClick);
} }
} }
} }

View File

@@ -15,6 +15,7 @@ namespace Darkmatter.Features.DrawingCatalog
private int _currentPage = 0; private int _currentPage = 0;
private int _totalPages = 1; private int _totalPages = 1;
private const int RowCount = 2;
private const int ItemPerPage = 8; //2 rows x 4 columns private const int ItemPerPage = 8; //2 rows x 4 columns
private readonly DrawingCatalogView _view; private readonly DrawingCatalogView _view;
@@ -24,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)
{ {
@@ -48,8 +55,14 @@ namespace Darkmatter.Features.DrawingCatalog
} }
private void HandleOpenColorBookSignal(OpenColorBookSignal signal) private void HandleOpenColorBookSignal(OpenColorBookSignal signal)
{
ShowCatalog();
}
private void ShowCatalog()
{ {
_view.Show(); _view.Show();
_eventBus.Publish(new ColorbookShownSignal());
} }
private void OnRightPageBtnClicked() private void OnRightPageBtnClicked()
@@ -84,7 +97,7 @@ namespace Darkmatter.Features.DrawingCatalog
private void OnArtBookBtnClicked() private void OnArtBookBtnClicked()
{ {
_eventBus.Publish(new OpenArtBookSignal(()=> _view.Show())); _eventBus.Publish(new OpenArtBookSignal(ShowCatalog));
_view.Hide(); _view.Hide();
} }
@@ -102,29 +115,86 @@ namespace Darkmatter.Features.DrawingCatalog
private async UniTask RefreshAsync(CancellationToken ct) private async UniTask RefreshAsync(CancellationToken ct)
{ {
var ids = _controller.VisibleIds; var newSprites = new List<Sprite>();
var vms = new List<CatalogItemVM>(ids.Count); var newTextures = new List<Texture2D>();
var applied = false;
_totalPages = (int)Math.Ceiling(ids.Count / (float)ItemPerPage); try
_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; if (ct.IsCancellationRequested) return;
var thumb = await _catalog.GetThumbnailAsync(id); _view.ResetScrollPosition();
vms.Add(new CatalogItemVM(id, thumb)); _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]);
} }
if (ct.IsCancellationRequested) return; return ordered;
_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() public void Dispose()
@@ -140,6 +210,7 @@ namespace Darkmatter.Features.DrawingCatalog
_openSubscription?.Dispose(); _openSubscription?.Dispose();
_cts.Cancel(); _cts.Cancel();
_cts.Dispose(); _cts.Dispose();
DestroyOwnedThumbnails();
} }
} }
} }

View File

@@ -62,9 +62,6 @@ namespace Darkmatter.Features.DrawingCatalog
while (_buttons.Count < items.Count) while (_buttons.Count < items.Count)
{ {
var button = Instantiate(buttonPrefab, content); var button = Instantiate(buttonPrefab, content);
var item = items[_buttons.Count];
button.Initialize(item.Id, item.Thumbnail,
() => { OnItemClicked?.Invoke(item.Id); });
_buttons.Add(button); _buttons.Add(button);
} }
@@ -75,6 +72,13 @@ namespace Darkmatter.Features.DrawingCatalog
Destroy(last.gameObject); 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); LayoutRebuilder.ForceRebuildLayoutImmediate(content);
} }
@@ -178,4 +182,4 @@ namespace Darkmatter.Features.DrawingCatalog
_suppressScrollEvents = false; _suppressScrollEvents = false;
} }
} }
} }

View File

@@ -68,7 +68,9 @@ namespace Darkmatter.Features.DrawingTemplates.Editor
_templates.Clear(); _templates.Clear();
_shapes.Clear(); _shapes.Clear();
_palettes.Clear(); _palettes.Clear();
_templates.AddRange(FindAllOfType<DrawingTemplateSO>()); _templates.AddRange(FindAllOfType<DrawingTemplateSO>()
.OrderBy(t => t.Difficulty)
.ThenBy(t => t.Id, StringComparer.OrdinalIgnoreCase));
_shapes.AddRange(FindAllOfType<ShapeSO>()); _shapes.AddRange(FindAllOfType<ShapeSO>());
_palettes.AddRange(FindAllOfType<ColorPaletteSO>()); _palettes.AddRange(FindAllOfType<ColorPaletteSO>());
} }
@@ -169,7 +171,7 @@ namespace Darkmatter.Features.DrawingTemplates.Editor
using (new EditorGUILayout.VerticalScope()) using (new EditorGUILayout.VerticalScope())
{ {
GUILayout.Label(string.IsNullOrEmpty(t.DisplayName) ? t.name : t.DisplayName, EditorStyles.boldLabel); GUILayout.Label(string.IsNullOrEmpty(t.DisplayName) ? t.name : t.DisplayName, EditorStyles.boldLabel);
GUILayout.Label("id: " + (string.IsNullOrEmpty(t.Id) ? "<unset>" : t.Id), EditorStyles.miniLabel); 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);
} }
if (GUILayout.Button("Select", GUILayout.Width(54))) if (GUILayout.Button("Select", GUILayout.Width(54)))
Select(t); Select(t);
@@ -206,8 +208,20 @@ namespace Darkmatter.Features.DrawingTemplates.Editor
EditorGUILayout.LabelField("Basics", EditorStyles.boldLabel); EditorGUILayout.LabelField("Basics", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(so.FindProperty("id"), new GUIContent("Id (Addressable Key)")); EditorGUILayout.PropertyField(so.FindProperty("id"), new GUIContent("Id (Addressable Key)"));
EditorGUILayout.PropertyField(so.FindProperty("displayName")); 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.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)"));
@@ -232,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();
} }
@@ -264,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();
} }
@@ -332,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();
@@ -412,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();
@@ -612,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

@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
using Cysharp.Threading.Tasks; using Cysharp.Threading.Tasks;
@@ -48,11 +49,11 @@ namespace Darkmatter.Features.DrawingTemplates.Systems
_byId[t.Id] = t; _byId[t.Id] = t;
} }
_allIds.Sort(); SortIds();
_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?");
@@ -61,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)
@@ -84,12 +85,22 @@ namespace Darkmatter.Features.DrawingTemplates.Systems
if (!_allIds.Contains(id)) if (!_allIds.Contains(id))
{ {
_allIds.Add(id); _allIds.Add(id);
_allIds.Sort(); SortIds();
} }
return t; 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() public void ReleaseAll()
{ {
if (!_initialized) return; if (!_initialized) return;

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
using Cysharp.Threading.Tasks; using Cysharp.Threading.Tasks;
using Darkmatter.Core;
using Darkmatter.Core.Contracts.Features.Capture; using Darkmatter.Core.Contracts.Features.Capture;
using Darkmatter.Core.Contracts.Features.Coloring; using Darkmatter.Core.Contracts.Features.Coloring;
using Darkmatter.Core.Contracts.Features.DrawingCatalog; using Darkmatter.Core.Contracts.Features.DrawingCatalog;
@@ -28,6 +29,7 @@ namespace Darkmatter.Features.GameplayFlow.Systems
public sealed class GameplayFlowController : IAsyncStartable, IGameplayFlowController, IDisposable public sealed class GameplayFlowController : IAsyncStartable, IGameplayFlowController, IDisposable
{ {
private const int AutosaveDebounceMs = 500; private const int AutosaveDebounceMs = 500;
private const float ThumbnailMinIntervalSec = 30f;
private readonly IProgressionSystem _progression; private readonly IProgressionSystem _progression;
private readonly IDrawingTemplateCatalog _catalog; private readonly IDrawingTemplateCatalog _catalog;
@@ -44,10 +46,13 @@ 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 IDisposable _assembledSub; private IDisposable _assembledSub;
private IDisposable _colorAppliedSub; private IDisposable _colorAppliedSub;
private IDisposable _drawingSelectedSub; private IDisposable _drawingSelectedSub;
private IDisposable _openColorbookSub;
private CancellationTokenSource _autosaveCts; private CancellationTokenSource _autosaveCts;
private CancellationTokenSource _scopeCts; private CancellationTokenSource _scopeCts;
@@ -76,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;
@@ -104,6 +130,10 @@ namespace Darkmatter.Features.GameplayFlow.Systems
_assembledSub = _bus.Subscribe<ShapeAssembledSignal>(OnShapeAssembled); _assembledSub = _bus.Subscribe<ShapeAssembledSignal>(OnShapeAssembled);
_colorAppliedSub = _bus.Subscribe<ColorAppliedSignal>(OnColorApplied); _colorAppliedSub = _bus.Subscribe<ColorAppliedSignal>(OnColorApplied);
_drawingSelectedSub = _bus.Subscribe<DrawingSelectedSignal>(OnDrawingSelected); _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); _loadingScreen.SetProgress(1f);
if (_phase == DrawingPhase.Coloring) if (_phase == DrawingPhase.Coloring)
@@ -119,66 +149,96 @@ 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)
{ {
await SaveCurrentAsync(CancellationToken.None); // A second navigation while one runs (e.g. Back pressed during the completion
_loadingScreen.Show(); // animation inside NextAsync) would interleave two saves and two Colorbook loads,
_shapeBuilder.Clear(); // and Clear() would rip the celebration apart mid-flight. First navigation wins.
_coloring.Clear(); if (_navigating) return;
await _scenes.LoadSceneAsync(nameof(GameScene.Colorbook), progress: null, cancellationToken: ct); _navigating = true;
await _scenes.UnloadSceneAsync(nameof(GameScene.Gameplay), progress: null, cancellationToken: ct); try
{
await SaveCurrentAsync(CancellationToken.None);
_loadingScreen.Show();
_shapeBuilder.Clear();
_coloring.Clear();
await _scenes.LoadSceneAsync(nameof(GameScene.Colorbook), 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)
{ {
await SaveCurrentAsync(ct); // One capture serves both the gallery write and the progress thumbnail — this used to
await _capture.CapturePngAsync(saveToGallery: true, ct); // run the full capture pipeline twice back to back.
var png = await _capture.CapturePngAsync(saveToGallery: true, ct);
await SaveCurrentAsync(ct, captureThumbnail: false, precapturedThumbnail: png);
} }
public async UniTask NextAsync(CancellationToken ct) public async UniTask NextAsync(CancellationToken ct)
{ {
// Drop any debounced autosave so it can't fire during/after the completion if (_navigating) return;
// animation and capture the animation (or an empty paper) into the thumbnail. _navigating = true;
_autosaveCts?.Cancel(); try
_autosaveCts?.Dispose();
_autosaveCts = null;
await SaveCurrentAsync(ct);
_refs.Confetti.Play();
_sfx.Play(SfxId.FireWorkLaunch);
_sfx.Play(SfxId.LevelComplete);
await _coloring.PlayCompletionAnimationAsync(ct);
_progression.MarkCompleted(_templateId);
var progressAfter = _progression.GetProgress(_templateId);
_bus.Publish(new DrawingCompletedSignal(_templateId, progressAfter?.completionCount ?? 1));
// Player has cleared the whole catalog → surface the "ran out of content" milestone. Guarded
// per session here; analytics dedupes it to once-ever.
if (!_allContentReported &&
_catalog.AllTemplateIds.Count > 0 &&
_progression.CompletedTemplateIds.Count >= _catalog.AllTemplateIds.Count)
{ {
_allContentReported = true; // Drop any debounced autosave so it can't fire during/after the completion
_bus.Publish(new AllContentCompletedSignal(_progression.CompletedTemplateIds.Count)); // animation and capture the animation (or an empty paper) into the thumbnail.
} _autosaveCts?.Cancel();
_autosaveCts?.Dispose();
_autosaveCts = null;
var nextId = _catalog.GetNextTemplate(_templateId); await SaveCurrentAsync(ct);
if (string.IsNullOrEmpty(nextId)) _refs.Confetti.Play();
{ _sfx.Play(SfxId.FireWorkLaunch);
_sfx.Play(SfxId.LevelComplete);
await _coloring.PlayCompletionAnimationAsync(ct);
_progression.MarkCompleted(_templateId);
var progressAfter = _progression.GetProgress(_templateId);
_bus.Publish(new DrawingCompletedSignal(_templateId, progressAfter?.completionCount ?? 1));
// Player has cleared the whole catalog → surface the "ran out of content" milestone. Guarded
// per session here; analytics dedupes it to once-ever.
if (!_allContentReported &&
_catalog.AllTemplateIds.Count > 0 &&
_progression.CompletedTemplateIds.Count >= _catalog.AllTemplateIds.Count)
{
_allContentReported = true;
_bus.Publish(new AllContentCompletedSignal(_progression.CompletedTemplateIds.Count));
}
var nextId = _catalog.GetNextTemplate(_templateId);
_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);
return; if (!string.IsNullOrEmpty(nextId))
_bus.Publish(new DrawingSelectedSignal(nextId));
}
finally
{
_navigating = false;
} }
_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);
_bus.Publish(new DrawingSelectedSignal(nextId));
} }
@@ -209,6 +269,11 @@ namespace Darkmatter.Features.GameplayFlow.Systems
DebouncedAutosaveAsync(_autosaveCts.Token).Forget(); DebouncedAutosaveAsync(_autosaveCts.Token).Forget();
} }
private void OnOpenColorBook(OpenColorBookSignal _)
{
BackAsync(CancellationToken.None).Forget();
}
private void OnDrawingSelected(DrawingSelectedSignal signal) private void OnDrawingSelected(DrawingSelectedSignal signal)
{ {
if (string.IsNullOrEmpty(signal.TemplateId)) return; if (string.IsNullOrEmpty(signal.TemplateId)) return;
@@ -218,13 +283,22 @@ namespace Darkmatter.Features.GameplayFlow.Systems
private async UniTaskVoid EditAsync(string newTemplateId) private async UniTaskVoid EditAsync(string newTemplateId)
{ {
await SaveCurrentAsync(CancellationToken.None); if (_navigating) return;
_loadingScreen.Show(); _navigating = true;
_shapeBuilder.Clear(); try
_coloring.Clear(); {
await _scenes.LoadSceneAsync(nameof(GameScene.Colorbook), progress: null, cancellationToken: default); await SaveCurrentAsync(CancellationToken.None);
await _scenes.UnloadSceneAsync(nameof(GameScene.Gameplay), progress: null, cancellationToken: default); _loadingScreen.Show();
_bus.Publish(new DrawingSelectedSignal(newTemplateId)); _shapeBuilder.Clear();
_coloring.Clear();
await _scenes.LoadSceneAsync(nameof(GameScene.Colorbook), progress: null, cancellationToken: default);
await _scenes.UnloadSceneAsync(nameof(GameScene.Gameplay), progress: null, cancellationToken: default);
_bus.Publish(new DrawingSelectedSignal(newTemplateId));
}
finally
{
_navigating = false;
}
} }
private async UniTaskVoid DebouncedAutosaveAsync(CancellationToken ct) private async UniTaskVoid DebouncedAutosaveAsync(CancellationToken ct)
@@ -232,7 +306,13 @@ namespace Darkmatter.Features.GameplayFlow.Systems
try try
{ {
await UniTask.Delay(AutosaveDebounceMs, cancellationToken: ct); await UniTask.Delay(AutosaveDebounceMs, cancellationToken: ct);
await SaveCurrentAsync(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);
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
@@ -240,7 +320,8 @@ namespace Darkmatter.Features.GameplayFlow.Systems
} }
} }
private async UniTask SaveCurrentAsync(CancellationToken ct) private async UniTask SaveCurrentAsync(CancellationToken ct, bool captureThumbnail = true,
byte[] precapturedThumbnail = null)
{ {
if (string.IsNullOrEmpty(_templateId)) return; if (string.IsNullOrEmpty(_templateId)) return;
@@ -263,7 +344,10 @@ namespace Darkmatter.Features.GameplayFlow.Systems
FirstCompletedUtc = existing?.FirstCompletedUtc, FirstCompletedUtc = existing?.FirstCompletedUtc,
}; };
var thumbnailPng = await _capture.CapturePngAsync(saveToGallery: false, ct); byte[] thumbnailPng = precapturedThumbnail;
if (thumbnailPng == null && captureThumbnail)
thumbnailPng = await _capture.CapturePngAsync(saveToGallery: false, ct);
if (thumbnailPng != null) _lastThumbnailTime = Time.realtimeSinceStartup;
await _progression.SaveProgressAsync(progress, thumbnailPng); await _progression.SaveProgressAsync(progress, thumbnailPng);
} }
@@ -284,6 +368,7 @@ namespace Darkmatter.Features.GameplayFlow.Systems
_assembledSub?.Dispose(); _assembledSub?.Dispose();
_colorAppliedSub?.Dispose(); _colorAppliedSub?.Dispose();
_drawingSelectedSub?.Dispose(); _drawingSelectedSub?.Dispose();
_openColorbookSub?.Dispose();
_autosaveCts?.Cancel(); _autosaveCts?.Cancel();
_autosaveCts?.Dispose(); _autosaveCts?.Dispose();
_scopeCts?.Cancel(); _scopeCts?.Cancel();

View File

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

View File

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

View File

@@ -0,0 +1,18 @@
{
"name": "Features.ImageCombiner.Editor",
"rootNamespace": "Darkmatter.Features.ImageCombiner.Editor",
"references": [
"GUID:bcae41f3415403d4da1f7aafb6d9a728"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: cba93615156299e4482e6b41e7db0199
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,633 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
using UnityEngine.UI;
namespace Darkmatter.Features.ImageCombiner.Editor
{
/// <summary>
/// Custom inspector for <see cref="UIImageCombiner"/>. Draws the configuration (save folder,
/// the two "after combining" checkboxes, resolution options) and performs the actual flatten:
/// it composites every child Image into one texture that matches the children's combined
/// bounds / position / aspect ratio, writes it as a PNG and (optionally) assigns it back.
/// </summary>
[CustomEditor(typeof(UIImageCombiner))]
public sealed class UIImageCombinerEditor : UnityEditor.Editor
{
private SerializedProperty _saveFolder;
private SerializedProperty _fileName;
private SerializedProperty _destroyChildren;
private SerializedProperty _disableChildren;
private SerializedProperty _recurse;
private SerializedProperty _includeInactive;
private SerializedProperty _pixelsPerUnit;
private SerializedProperty _maxDimension;
private SerializedProperty _trimTransparent;
private SerializedProperty _alphaThreshold;
private SerializedProperty _assignToSelf;
/// <summary>Relative aspect-ratio difference above which a stretched child is reported.</summary>
private const float AspectMismatchTolerance = 0.01f;
private void OnEnable()
{
_saveFolder = serializedObject.FindProperty("_saveFolder");
_fileName = serializedObject.FindProperty("_fileName");
_destroyChildren = serializedObject.FindProperty("_destroyChildrenAfterCombine");
_disableChildren = serializedObject.FindProperty("_disableChildrenAfterCombine");
_recurse = serializedObject.FindProperty("_recurseIntoChildren");
_includeInactive = serializedObject.FindProperty("_includeInactiveChildren");
_pixelsPerUnit = serializedObject.FindProperty("_pixelsPerUnit");
_maxDimension = serializedObject.FindProperty("_maxDimension");
_trimTransparent = serializedObject.FindProperty("_trimTransparent");
_alphaThreshold = serializedObject.FindProperty("_alphaThreshold");
_assignToSelf = serializedObject.FindProperty("_assignCombinedToSelf");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
var combiner = (UIImageCombiner)target;
EditorGUILayout.LabelField("Output location", EditorStyles.boldLabel);
using (new EditorGUILayout.HorizontalScope())
{
EditorGUILayout.PropertyField(_saveFolder, new GUIContent("Save Folder"));
if (GUILayout.Button("Browse", GUILayout.Width(64)))
BrowseForFolder(_saveFolder);
}
EditorGUILayout.PropertyField(_fileName, new GUIContent("File Name (opt.)"));
EditorGUILayout.Space(6);
EditorGUILayout.LabelField("After combining", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(_destroyChildren, new GUIContent("Destroy Children"));
using (new EditorGUI.DisabledScope(_destroyChildren.boolValue))
EditorGUILayout.PropertyField(_disableChildren, new GUIContent("Disable Children"));
if (_destroyChildren.boolValue && _disableChildren.boolValue)
EditorGUILayout.HelpBox("Both ticked: children will be destroyed (destroy wins).", MessageType.Info);
EditorGUILayout.Space(6);
EditorGUILayout.LabelField("What to combine", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(_recurse, new GUIContent("Recurse Into Children"));
EditorGUILayout.PropertyField(_includeInactive, new GUIContent("Include Inactive"));
EditorGUILayout.Space(6);
EditorGUILayout.LabelField("Resolution", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(_pixelsPerUnit, new GUIContent("Pixels Per Unit (0 = auto)"));
EditorGUILayout.PropertyField(_maxDimension, new GUIContent("Max Dimension"));
EditorGUILayout.Space(6);
EditorGUILayout.LabelField("Trimming", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(_trimTransparent, new GUIContent("Trim Transparent"));
using (new EditorGUI.DisabledScope(!_trimTransparent.boolValue))
EditorGUILayout.PropertyField(_alphaThreshold, new GUIContent("Alpha Threshold"));
EditorGUILayout.Space(6);
EditorGUILayout.LabelField("Assignment", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(_assignToSelf, new GUIContent("Assign Combined To Self"));
serializedObject.ApplyModifiedProperties();
EditorGUILayout.Space(10);
using (new EditorGUI.DisabledScope(Application.isPlaying))
{
if (GUILayout.Button("Combine Images", GUILayout.Height(32)))
Combine(combiner);
}
if (Application.isPlaying)
EditorGUILayout.HelpBox("Exit play mode to combine images.", MessageType.Warning);
}
private static void BrowseForFolder(SerializedProperty folderProp)
{
string start = folderProp.stringValue;
if (string.IsNullOrEmpty(start)) start = "Assets";
string abs = EditorUtility.OpenFolderPanel("Choose save folder", start, "");
if (string.IsNullOrEmpty(abs)) return;
// Convert to a project-relative "Assets/..." path when the chosen folder is inside the project.
string dataPath = Application.dataPath.Replace('\\', '/');
abs = abs.Replace('\\', '/');
if (abs == dataPath)
folderProp.stringValue = "Assets";
else if (abs.StartsWith(dataPath + "/"))
folderProp.stringValue = "Assets" + abs.Substring(dataPath.Length);
else
folderProp.stringValue = abs; // outside the project keep the absolute path
folderProp.serializedObject.ApplyModifiedProperties();
}
// ------------------------------------------------------------------ combine
private static void Combine(UIImageCombiner combiner)
{
var self = (RectTransform)combiner.transform;
var images = CollectImages(combiner, self);
if (images.Count == 0)
{
EditorUtility.DisplayDialog("UI Image Combiner",
"No child Image components found to combine.", "OK");
return;
}
// 1) Combined world bounds from every child's rect corners.
if (!TryComputeBounds(images, out Vector2 boundsMin, out Vector2 boundsMax))
{
EditorUtility.DisplayDialog("UI Image Combiner",
"The combined bounds are empty (zero size).", "OK");
return;
}
Vector2 boundsSize = boundsMax - boundsMin;
// 2) Output resolution.
float ppu = combiner.PixelsPerUnit > 0f
? combiner.PixelsPerUnit
: AutoPixelsPerUnit(images);
int width = Mathf.Max(1, Mathf.RoundToInt(boundsSize.x * ppu));
int height = Mathf.Max(1, Mathf.RoundToInt(boundsSize.y * ppu));
int maxDim = combiner.MaxDimension;
int longest = Mathf.Max(width, height);
if (longest > maxDim)
{
float s = maxDim / (float)longest;
width = Mathf.Max(1, Mathf.RoundToInt(width * s));
height = Mathf.Max(1, Mathf.RoundToInt(height * s));
}
bool rotationWarned = false;
var pixelCache = new Dictionary<Texture, (Color32[] px, int w, int h)>();
var output = new Color[width * height]; // straight alpha, (0,0) = bottom-left
float worldPerPxX = boundsSize.x / width;
float worldPerPxY = boundsSize.y / height;
try
{
for (int i = 0; i < images.Count; i++)
{
Image img = images[i];
EditorUtility.DisplayProgressBar("UI Image Combiner",
$"Compositing {img.name} ({i + 1}/{images.Count})", (i + 1f) / images.Count);
if (!TryGetAxisAlignedRect(img, out Vector2 imgMin, out Vector2 imgMax, out bool rotated))
continue;
if (rotated && !rotationWarned)
{
Debug.LogWarning("[UIImageCombiner] One or more images are rotated/skewed; " +
"they are composited using their axis-aligned bounds and may " +
"look distorted.", img);
rotationWarned = true;
}
Color tint = img.color;
Sprite sprite = img.sprite;
// Rect the sprite is actually drawn into (accounts for Preserve Aspect).
Vector2 drawMin = imgMin;
Vector2 drawSize = imgMax - imgMin;
if (sprite != null && drawSize.x > 0f && drawSize.y > 0f)
{
if (img.preserveAspect)
{
FitPreserveAspect(sprite, ref drawMin, ref drawSize);
}
else if (sprite.rect.height > 0f)
{
// Preserve Aspect is off, so the whole sprite is stretched to fill the
// RectTransform. If their aspect ratios differ, the content (and any
// transparent padding) gets squashed/stretched — warn and name the child.
float spriteAspect = sprite.rect.width / sprite.rect.height;
float rectAspect = drawSize.x / drawSize.y;
if (spriteAspect > 0f &&
Mathf.Abs(rectAspect - spriteAspect) > spriteAspect * AspectMismatchTolerance)
{
Debug.LogWarning(
$"[UIImageCombiner] \"{img.name}\" will be stretched: sprite is " +
$"{sprite.rect.width:0}x{sprite.rect.height:0} (aspect {spriteAspect:0.###}) " +
$"but its RectTransform aspect is {rectAspect:0.###} and Preserve Aspect is " +
"off. Enable \"Preserve Aspect\" on the Image, or match the RectTransform to " +
"the sprite's aspect ratio, to avoid distortion.", img);
}
}
}
// A trimmed / tight-mesh sprite reports its FULL frame as sprite.rect but only
// draws the opaque texels described by GetOuterUV. A UI Image maps that outer UV
// onto the padding-inset sub-rect of its RectTransform and leaves the transparent
// border empty — it does NOT stretch the artwork to fill the frame. Shrink the draw
// rect to that same sub-rect so each child keeps its real size/position and only its
// visible pixels are composited (the transparent background is dropped).
if (sprite != null && drawSize.x > 0f && drawSize.y > 0f)
InsetBySpritePadding(sprite, ref drawMin, ref drawSize);
if (drawSize.x <= 0f || drawSize.y <= 0f)
continue;
(Color32[] px, int tw, int th) src = default;
Rect texRect = default;
if (sprite != null)
{
src = GetPixels(sprite.texture, pixelCache);
// Use the sprite's OUTER UV rect (the full quad, transparent padding
// included) — the same region a UI Image maps onto its RectTransform when
// "Use Sprite Mesh" is off. sprite.textureRect is the tight, transparent-
// trimmed rect (Sprite Mesh Type = Tight), which would map only the drawn
// pixels across the whole rect and stretch the artwork to fill it.
Vector4 uv = UnityEngine.Sprites.DataUtility.GetOuterUV(sprite);
texRect = new Rect(uv.x * src.tw, uv.y * src.th,
(uv.z - uv.x) * src.tw, (uv.w - uv.y) * src.th);
}
// Pixel range this image can touch.
int px0 = Mathf.Clamp(Mathf.FloorToInt((drawMin.x - boundsMin.x) / worldPerPxX), 0, width);
int px1 = Mathf.Clamp(Mathf.CeilToInt((drawMin.x + drawSize.x - boundsMin.x) / worldPerPxX), 0, width);
int py0 = Mathf.Clamp(Mathf.FloorToInt((drawMin.y - boundsMin.y) / worldPerPxY), 0, height);
int py1 = Mathf.Clamp(Mathf.CeilToInt((drawMin.y + drawSize.y - boundsMin.y) / worldPerPxY), 0, height);
for (int y = py0; y < py1; y++)
{
float worldY = boundsMin.y + (y + 0.5f) * worldPerPxY;
float v = (worldY - drawMin.y) / drawSize.y;
if (v < 0f || v > 1f) continue;
int row = y * width;
for (int x = px0; x < px1; x++)
{
float worldX = boundsMin.x + (x + 0.5f) * worldPerPxX;
float u = (worldX - drawMin.x) / drawSize.x;
if (u < 0f || u > 1f) continue;
Color srcColor;
if (sprite != null)
{
float texPx = texRect.x + u * texRect.width;
float texPy = texRect.y + v * texRect.height;
srcColor = SampleBilinear(src.px, src.tw, src.th, texPx, texPy) * tint;
}
else
{
srcColor = tint; // Image with no sprite = solid colour quad.
}
if (srcColor.a <= 0f) continue;
output[row + x] = Over(srcColor, output[row + x]);
}
}
}
}
finally
{
EditorUtility.ClearProgressBar();
}
// 2b) Alpha-trim: crop away transparent margins and shrink the bounds to the real content
// so the output PNG (and the resized parent) tightly wrap the visible pixels.
if (combiner.TrimTransparent)
{
if (TryTrim(output, width, height, combiner.AlphaThreshold,
out Color[] trimmed, out int tw, out int th,
out int offsetX, out int offsetY))
{
boundsMin = new Vector2(boundsMin.x + offsetX * worldPerPxX,
boundsMin.y + offsetY * worldPerPxY);
boundsMax = new Vector2(boundsMin.x + tw * worldPerPxX,
boundsMin.y + th * worldPerPxY);
boundsSize = boundsMax - boundsMin;
output = trimmed;
width = tw;
height = th;
}
else
{
Debug.LogWarning("[UIImageCombiner] The combined image is fully transparent; " +
"nothing to trim.", combiner);
}
}
// 3) Build the PNG.
var texture = new Texture2D(width, height, TextureFormat.RGBA32, false);
texture.SetPixels(output);
texture.Apply();
byte[] png = texture.EncodeToPNG();
Object.DestroyImmediate(texture);
// 4) Save it.
string relFolder = combiner.SaveFolder.Replace('\\', '/').TrimEnd('/');
if (string.IsNullOrEmpty(relFolder)) relFolder = "Assets";
string fileName = SanitizeFileName(combiner.FileName);
if (!fileName.EndsWith(".png", System.StringComparison.OrdinalIgnoreCase))
fileName += ".png";
string projectRoot = Directory.GetParent(Application.dataPath).FullName;
string absFolder = Path.IsPathRooted(relFolder)
? relFolder
: Path.Combine(projectRoot, relFolder);
Directory.CreateDirectory(absFolder);
string absFile = Path.Combine(absFolder, fileName);
File.WriteAllBytes(absFile, png);
// 5) If it is inside the project, import as a Sprite and assign it back.
string assetPath = ToAssetPath(absFile, projectRoot);
Sprite combinedSprite = null;
if (assetPath != null)
{
AssetDatabase.ImportAsset(assetPath, ImportAssetOptions.ForceUpdate);
if (AssetImporter.GetAtPath(assetPath) is TextureImporter importer)
{
importer.textureType = TextureImporterType.Sprite;
importer.spriteImportMode = SpriteImportMode.Single;
importer.alphaIsTransparency = true;
importer.mipmapEnabled = false;
importer.SaveAndReimport();
}
combinedSprite = AssetDatabase.LoadAssetAtPath<Sprite>(assetPath);
}
// 6) Assign to self + resize to the combined bounds.
Undo.RegisterFullObjectHierarchyUndo(self.gameObject, "Combine Images");
if (combiner.AssignCombinedToSelf && combinedSprite != null)
{
var image = self.GetComponent<Image>();
if (image == null) image = Undo.AddComponent<Image>(self.gameObject);
image.sprite = combinedSprite;
image.type = Image.Type.Simple;
image.preserveAspect = false;
image.color = Color.white;
image.enabled = true;
Vector3 lossy = self.lossyScale;
self.anchorMin = self.anchorMax = new Vector2(0.5f, 0.5f);
self.pivot = new Vector2(0.5f, 0.5f);
self.sizeDelta = new Vector2(
boundsSize.x / (Mathf.Approximately(lossy.x, 0f) ? 1f : lossy.x),
boundsSize.y / (Mathf.Approximately(lossy.y, 0f) ? 1f : lossy.y));
Vector2 center = (boundsMin + boundsMax) * 0.5f;
self.position = new Vector3(center.x, center.y, self.position.z);
EditorUtility.SetDirty(image);
}
// 7) Destroy or disable the combined children.
var childObjects = images.Select(im => im.gameObject).Distinct().ToList();
if (combiner.DestroyChildrenAfterCombine)
{
foreach (var go in childObjects)
if (go != null && go != self.gameObject)
Undo.DestroyObjectImmediate(go);
}
else if (combiner.DisableChildrenAfterCombine)
{
foreach (var go in childObjects)
if (go != null && go != self.gameObject)
go.SetActive(false);
}
EditorUtility.SetDirty(combiner);
if (!Application.isPlaying)
UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(combiner.gameObject.scene);
if (combinedSprite != null)
{
EditorGUIUtility.PingObject(combinedSprite);
Selection.activeObject = combinedSprite;
}
Debug.Log($"[UIImageCombiner] Combined {images.Count} image(s) into {width}x{height} " +
$"PNG at \"{(assetPath ?? absFile)}\".", combiner);
}
// ------------------------------------------------------------------ helpers
private static List<Image> CollectImages(UIImageCombiner combiner, RectTransform self)
{
var result = new List<Image>();
if (combiner.RecurseIntoChildren)
{
// GetComponentsInChildren returns parent-first, depth-first (== UI draw order).
foreach (var img in self.GetComponentsInChildren<Image>(combiner.IncludeInactiveChildren))
{
if (img.gameObject == self.gameObject) continue;
if (!img.enabled) continue;
result.Add(img);
}
}
else
{
for (int c = 0; c < self.childCount; c++)
{
var child = self.GetChild(c);
if (!combiner.IncludeInactiveChildren && !child.gameObject.activeInHierarchy) continue;
var img = child.GetComponent<Image>();
if (img != null && img.enabled) result.Add(img);
}
}
return result;
}
private static bool TryComputeBounds(List<Image> images, out Vector2 min, out Vector2 max)
{
min = new Vector2(float.MaxValue, float.MaxValue);
max = new Vector2(float.MinValue, float.MinValue);
var corners = new Vector3[4];
bool any = false;
foreach (var img in images)
{
((RectTransform)img.transform).GetWorldCorners(corners);
for (int k = 0; k < 4; k++)
{
min.x = Mathf.Min(min.x, corners[k].x);
min.y = Mathf.Min(min.y, corners[k].y);
max.x = Mathf.Max(max.x, corners[k].x);
max.y = Mathf.Max(max.y, corners[k].y);
}
any = true;
}
return any && max.x > min.x && max.y > min.y;
}
/// <summary>
/// Finds the tight bounding box of pixels whose alpha exceeds <paramref name="threshold"/> and
/// returns a cropped copy plus the bottom-left offset (in pixels) of that box within the source.
/// </summary>
private static bool TryTrim(Color[] src, int width, int height, float threshold,
out Color[] cropped, out int newW, out int newH, out int offsetX, out int offsetY)
{
int minX = width, minY = height, maxX = -1, maxY = -1;
for (int y = 0; y < height; y++)
{
int row = y * width;
for (int x = 0; x < width; x++)
{
if (src[row + x].a > threshold)
{
if (x < minX) minX = x;
if (x > maxX) maxX = x;
if (y < minY) minY = y;
if (y > maxY) maxY = y;
}
}
}
if (maxX < minX || maxY < minY)
{
cropped = null;
newW = newH = offsetX = offsetY = 0;
return false;
}
newW = maxX - minX + 1;
newH = maxY - minY + 1;
offsetX = minX;
offsetY = minY;
cropped = new Color[newW * newH];
for (int y = 0; y < newH; y++)
{
int srcRow = (minY + y) * width + minX;
int dstRow = y * newW;
for (int x = 0; x < newW; x++)
cropped[dstRow + x] = src[srcRow + x];
}
return true;
}
/// <summary>Axis-aligned world rect of an image, plus whether it looked rotated/skewed.</summary>
private static bool TryGetAxisAlignedRect(Image img, out Vector2 min, out Vector2 max, out bool rotated)
{
var corners = new Vector3[4]; // 0=BL 1=TL 2=TR 3=BR
((RectTransform)img.transform).GetWorldCorners(corners);
min = new Vector2(
Mathf.Min(Mathf.Min(corners[0].x, corners[1].x), Mathf.Min(corners[2].x, corners[3].x)),
Mathf.Min(Mathf.Min(corners[0].y, corners[1].y), Mathf.Min(corners[2].y, corners[3].y)));
max = new Vector2(
Mathf.Max(Mathf.Max(corners[0].x, corners[1].x), Mathf.Max(corners[2].x, corners[3].x)),
Mathf.Max(Mathf.Max(corners[0].y, corners[1].y), Mathf.Max(corners[2].y, corners[3].y)));
// Rotated if bottom edge is not horizontal or left edge is not vertical.
const float eps = 1e-3f;
rotated = Mathf.Abs(corners[0].y - corners[3].y) > eps ||
Mathf.Abs(corners[0].x - corners[1].x) > eps;
return (max.x - min.x) > 0f && (max.y - min.y) > 0f;
}
/// <summary>
/// Shrinks a draw rect from the sprite's full frame (<see cref="Sprite.rect"/>) to the tight
/// sub-rect its opaque texels actually occupy. Trimmed / tight-mesh sprites report the full
/// frame as their rect but only render the region returned by GetOuterUV; a UI Image maps that
/// region onto the padding-inset part of the RectTransform and leaves the border transparent.
/// Applying the same inset here keeps the artwork at its true size/position (no stretching) and
/// means only the visible pixels — not the transparent background — get composited.
/// </summary>
private static void InsetBySpritePadding(Sprite sprite, ref Vector2 min, ref Vector2 size)
{
float rw = sprite.rect.width, rh = sprite.rect.height;
if (rw <= 0f || rh <= 0f) return;
// GetPadding returns the empty border (left, bottom, right, top) in pixels of sprite.rect.
Vector4 pad = UnityEngine.Sprites.DataUtility.GetPadding(sprite);
if (pad == Vector4.zero) return;
min = new Vector2(min.x + (pad.x / rw) * size.x, min.y + (pad.y / rh) * size.y);
size = new Vector2(size.x * (1f - (pad.x + pad.z) / rw),
size.y * (1f - (pad.y + pad.w) / rh));
}
private static void FitPreserveAspect(Sprite sprite, ref Vector2 min, ref Vector2 size)
{
float spriteAspect = sprite.rect.width / sprite.rect.height;
float rectAspect = size.x / size.y;
Vector2 drawn;
if (rectAspect > spriteAspect) // rect wider than sprite -> fit height
drawn = new Vector2(size.y * spriteAspect, size.y);
else
drawn = new Vector2(size.x, size.x / spriteAspect);
min += (size - drawn) * 0.5f;
size = drawn;
}
private static float AutoPixelsPerUnit(List<Image> images)
{
float ppu = 0f;
var corners = new Vector3[4];
foreach (var img in images)
{
if (img.sprite == null) continue;
((RectTransform)img.transform).GetWorldCorners(corners);
float worldW = Vector2.Distance(corners[0], corners[3]);
float worldH = Vector2.Distance(corners[0], corners[1]);
if (worldW > 0f) ppu = Mathf.Max(ppu, img.sprite.rect.width / worldW);
if (worldH > 0f) ppu = Mathf.Max(ppu, img.sprite.rect.height / worldH);
}
return ppu > 0f ? ppu : 100f; // fallback for all-solid-colour cases
}
/// <summary>Reads a texture's pixels via a Blit (works even when the texture is not marked readable).</summary>
private static (Color32[] px, int w, int h) GetPixels(
Texture tex, Dictionary<Texture, (Color32[], int, int)> cache)
{
if (tex == null) return (new Color32[] { new Color32(255, 255, 255, 255) }, 1, 1);
if (cache.TryGetValue(tex, out var cached)) return cached;
int w = tex.width, h = tex.height;
RenderTexture rt = RenderTexture.GetTemporary(w, h, 0, RenderTextureFormat.ARGB32);
RenderTexture prev = RenderTexture.active;
Graphics.Blit(tex, rt);
RenderTexture.active = rt;
var tmp = new Texture2D(w, h, TextureFormat.RGBA32, false);
tmp.ReadPixels(new Rect(0, 0, w, h), 0, 0);
tmp.Apply();
RenderTexture.active = prev;
RenderTexture.ReleaseTemporary(rt);
Color32[] px = tmp.GetPixels32();
Object.DestroyImmediate(tmp);
var entry = (px, w, h);
cache[tex] = entry;
return entry;
}
private static Color SampleBilinear(Color32[] px, int w, int h, float x, float y)
{
x = Mathf.Clamp(x - 0.5f, 0f, w - 1f);
y = Mathf.Clamp(y - 0.5f, 0f, h - 1f);
int x0 = (int)x, y0 = (int)y;
int x1 = Mathf.Min(x0 + 1, w - 1);
int y1 = Mathf.Min(y0 + 1, h - 1);
float fx = x - x0, fy = y - y0;
Color c00 = px[y0 * w + x0];
Color c10 = px[y0 * w + x1];
Color c01 = px[y1 * w + x0];
Color c11 = px[y1 * w + x1];
return Color.Lerp(Color.Lerp(c00, c10, fx), Color.Lerp(c01, c11, fx), fy);
}
/// <summary>Straight-alpha "source over destination".</summary>
private static Color Over(Color src, Color dst)
{
float outA = src.a + dst.a * (1f - src.a);
if (outA <= 0f) return new Color(0f, 0f, 0f, 0f);
float inv = 1f / outA;
return new Color(
(src.r * src.a + dst.r * dst.a * (1f - src.a)) * inv,
(src.g * src.a + dst.g * dst.a * (1f - src.a)) * inv,
(src.b * src.a + dst.b * dst.a * (1f - src.a)) * inv,
outA);
}
private static string SanitizeFileName(string name)
{
if (string.IsNullOrWhiteSpace(name)) name = "CombinedImage";
foreach (char c in Path.GetInvalidFileNameChars())
name = name.Replace(c, '_');
return name;
}
private static string ToAssetPath(string absFile, string projectRoot)
{
string full = Path.GetFullPath(absFile).Replace('\\', '/');
string root = Path.GetFullPath(projectRoot).Replace('\\', '/');
if (!full.StartsWith(root + "/")) return null;
string rel = full.Substring(root.Length + 1);
return rel.StartsWith("Assets/") || rel == "Assets" ? rel : null;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 4ff6d3964378b694ba9c7ba67b3af897

View File

@@ -0,0 +1,14 @@
{
"name": "Features.ImageCombiner",
"rootNamespace": "Darkmatter.Features.ImageCombiner",
"references": [],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: bcae41f3415403d4da1f7aafb6d9a728
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,85 @@
using UnityEngine;
using UnityEngine.UI;
namespace Darkmatter.Features.ImageCombiner
{
/// <summary>
/// Attach to a parent UI object that holds several child <see cref="Image"/> objects
/// (each with a transparent background). The custom inspector's "Combine Images" button
/// flattens every child Image into a single PNG that has the exact combined bounds,
/// position and aspect ratio of the children, saves it to the folder you specify, and
/// assigns it back to this object as one Image.
///
/// All of the actual combining/saving lives in the editor-only inspector
/// (Editor/UIImageCombinerEditor.cs); this component only stores the configuration so it
/// still compiles into player builds.
/// </summary>
[DisallowMultipleComponent]
[RequireComponent(typeof(RectTransform))]
[AddComponentMenu("Darkmatter/UI/UI Image Combiner")]
public sealed class UIImageCombiner : MonoBehaviour
{
[Header("Output location")]
[Tooltip("Folder the combined PNG is written to. Use a project-relative path that starts " +
"with \"Assets/\" (e.g. Assets/Darkmatter/CombinedImages) so it can be imported as a " +
"Sprite and assigned back automatically. An absolute path outside the project just " +
"writes the file to disk.")]
[SerializeField] private string _saveFolder = "Assets/Darkmatter/CombinedImages";
[Tooltip("File name for the combined PNG (without extension). Leave empty to use this " +
"GameObject's name.")]
[SerializeField] private string _fileName = "";
[Header("After combining")]
[Tooltip("Destroy the child GameObjects whose images were combined. Takes priority over " +
"\"Disable Children\" when both are ticked.")]
[SerializeField] private bool _destroyChildrenAfterCombine = false;
[Tooltip("Disable (SetActive false) the child GameObjects whose images were combined.")]
[SerializeField] private bool _disableChildrenAfterCombine = false;
[Header("What to combine")]
[Tooltip("Also combine Images found on nested (grand-)children, not just direct children.")]
[SerializeField] private bool _recurseIntoChildren = true;
[Tooltip("Include children that are currently inactive in the hierarchy.")]
[SerializeField] private bool _includeInactiveChildren = false;
[Header("Resolution")]
[Tooltip("Pixels per world unit for the output texture. 0 = auto: matched to the highest " +
"resolution source sprite so no detail is lost.")]
[SerializeField] private float _pixelsPerUnit = 0f;
[Tooltip("Hard cap for the longest side of the output texture (keeps huge layouts from " +
"producing enormous PNGs). Aspect ratio is preserved.")]
[SerializeField] private int _maxDimension = 4096;
[Header("Trimming")]
[Tooltip("Crop the final combined image to the tight bounding box of non-transparent pixels, " +
"so empty/transparent margins are NOT included. The assigned Image is repositioned " +
"and resized to match the trimmed content, keeping each child at its real size.")]
[SerializeField] private bool _trimTransparent = true;
[Tooltip("Pixels with alpha at or below this value are treated as empty when trimming (0..1). " +
"Leave at 0 to keep soft/anti-aliased edges; raise slightly to also trim faint fringes.")]
[Range(0f, 1f)]
[SerializeField] private float _alphaThreshold = 0f;
[Header("Assignment")]
[Tooltip("Add/reuse an Image on THIS object, assign the combined sprite to it, and resize " +
"this RectTransform to the combined bounds so it lands at the same place and size.")]
[SerializeField] private bool _assignCombinedToSelf = true;
public string SaveFolder => _saveFolder;
public string FileName => string.IsNullOrWhiteSpace(_fileName) ? name : _fileName.Trim();
public bool DestroyChildrenAfterCombine => _destroyChildrenAfterCombine;
public bool DisableChildrenAfterCombine => _disableChildrenAfterCombine;
public bool RecurseIntoChildren => _recurseIntoChildren;
public bool IncludeInactiveChildren => _includeInactiveChildren;
public float PixelsPerUnit => _pixelsPerUnit;
public int MaxDimension => Mathf.Max(1, _maxDimension);
public bool TrimTransparent => _trimTransparent;
public float AlphaThreshold => _alphaThreshold;
public bool AssignCombinedToSelf => _assignCombinedToSelf;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 85c9da43c395b794fb2180a6da9f6511

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,16 +86,27 @@ public sealed class ProgressionRepository
{ {
if (string.IsNullOrEmpty(templateId) || png == null || png.Length == 0) return; if (string.IsNullOrEmpty(templateId) || png == null || png.Length == 0) return;
Directory.CreateDirectory(ThumbnailDirectory()); // Never let thumbnail IO (disk full, permission churn) throw into the save path:
var path = ThumbnailPath(templateId); // saves run before every Back/Next navigation, and an unhandled IOException there
var tmp = path + ".tmp"; // aborts the scene transition and traps the user in gameplay. Losing a thumbnail
// is fine — the colors themselves persist via PlayerPrefs.
await UniTask.RunOnThreadPool(() => try
{ {
File.WriteAllBytes(tmp, png); Directory.CreateDirectory(ThumbnailDirectory());
if (File.Exists(path)) File.Delete(path); var path = ThumbnailPath(templateId);
File.Move(tmp, path); 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)
{
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

@@ -39,6 +39,11 @@ namespace Darkmatter.Services.Ads
[SerializeField, Min(0)] [SerializeField, Min(0)]
private int maxInterstitialsPerSession = 8; 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 bool IsInitialized => _initialized;
public event Action<AdFormat, AdLoadState> LoadStateChanged; public event Action<AdFormat, AdLoadState> LoadStateChanged;
@@ -653,11 +658,12 @@ namespace Darkmatter.Services.Ads
ad.OnAdClicked += () => LogAdClicked(format); ad.OnAdClicked += () => LogAdClicked(format);
} }
// Ad revenue: GA4-recommended ad_impression carries value+currency from AdMob's paid callback — // AdMob/Firebase can auto-log ad_impression for linked apps. Manual logging is opt-in so one
// this is what surfaces ad revenue in Firebase/GA4. Ad callbacks fire on the Unity main thread // shown ad does not become two GA4 ad_impression events.
// (RaiseAdEventsOnUnityMainThread = true), so logging straight to analytics is safe.
private void LogAdImpression(AdFormat format, AdValue value) private void LogAdImpression(AdFormat format, AdValue value)
{ {
if (!logManualAdImpressionEvents) return;
_analytics?.LogEvent(AnalyticsEvents.AdImpression, new Dictionary<string, object> _analytics?.LogEvent(AnalyticsEvents.AdImpression, new Dictionary<string, object>
{ {
[AnalyticsParams.AdPlatform] = "AdMob", [AnalyticsParams.AdPlatform] = "AdMob",
@@ -736,4 +742,4 @@ namespace Darkmatter.Services.Ads
LoadStateChanged?.Invoke(format, state); LoadStateChanged?.Invoke(format, state);
} }
} }
} }

View File

@@ -56,8 +56,9 @@ namespace Darkmatter.Services.Analytics
_subs.Add(_bus.Subscribe<IntroCompletedSignal>(_ => _analytics.LogEvent(AnalyticsEvents.IntroCompleted))); _subs.Add(_bus.Subscribe<IntroCompletedSignal>(_ => _analytics.LogEvent(AnalyticsEvents.IntroCompleted)));
_subs.Add(_bus.Subscribe<PlayBtnClickedSignal>(_ => _analytics.LogEvent(AnalyticsEvents.PlayClicked))); _subs.Add(_bus.Subscribe<PlayBtnClickedSignal>(_ => _analytics.LogEvent(AnalyticsEvents.PlayClicked)));
// Navigation // Navigation. ColorbookShownSignal (not OpenColorBookSignal, which is the show-view command
_subs.Add(_bus.Subscribe<OpenColorBookSignal>(_ => _analytics.LogEvent(AnalyticsEvents.ColorbookOpened))); // and misses scene-entry paths) fires on every way the catalog becomes visible.
_subs.Add(_bus.Subscribe<ColorbookShownSignal>(_ => OnColorbookShown()));
_subs.Add(_bus.Subscribe<OpenArtBookSignal>(_ => _analytics.LogEvent(AnalyticsEvents.ArtbookOpened))); _subs.Add(_bus.Subscribe<OpenArtBookSignal>(_ => _analytics.LogEvent(AnalyticsEvents.ArtbookOpened)));
_subs.Add(_bus.Subscribe<ReturnToMainMenuSignal>(_ => OnReturnToMainMenu())); _subs.Add(_bus.Subscribe<ReturnToMainMenuSignal>(_ => OnReturnToMainMenu()));
@@ -90,6 +91,10 @@ namespace Darkmatter.Services.Analytics
private void OnDrawingSelected(string drawingId) 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. // A new selection while a drawing is still open (uncompleted) = the previous one was abandoned.
EndActiveDrawingIfAbandoned(); EndActiveDrawingIfAbandoned();
@@ -156,7 +161,7 @@ namespace Darkmatter.Services.Analytics
if (dur >= 0f) p[AnalyticsParams.DurationSeconds] = dur; if (dur >= 0f) p[AnalyticsParams.DurationSeconds] = dur;
_analytics.LogEvent(AnalyticsEvents.DrawingCompleted, p); _analytics.LogEvent(AnalyticsEvents.DrawingCompleted, p);
// GA4 recommended games event (lights up built-in level funnels). // Companion to GA4's level_start.
_analytics.LogEvent(AnalyticsEvents.LevelComplete, new Dictionary<string, object> _analytics.LogEvent(AnalyticsEvents.LevelComplete, new Dictionary<string, object>
{ {
[AnalyticsParams.LevelName] = s.TemplateId, [AnalyticsParams.LevelName] = s.TemplateId,
@@ -171,16 +176,24 @@ namespace Darkmatter.Services.Analytics
if (PlayerPrefs.GetInt(AllContentKey, 0) == 1) return; // once ever if (PlayerPrefs.GetInt(AllContentKey, 0) == 1) return; // once ever
PlayerPrefs.SetInt(AllContentKey, 1); PlayerPrefs.SetInt(AllContentKey, 1);
PlayerPrefs.Save(); PlayerPrefs.Save();
_analytics.LogEvent(AnalyticsEvents.AllContentCompleted, _analytics.LogEvent(AnalyticsEvents.AllContentCompleted, new Dictionary<string, object>
AnalyticsParams.CompletionCount, s.CompletedCount.ToString()); {
[AnalyticsParams.CompletionCount] = s.CompletedCount,
});
} }
private void OnGallerySaveCompleted(GallerySaveCompletedSignal s) private void OnGallerySaveCompleted(GallerySaveCompletedSignal s)
{ {
_analytics.LogEvent(AnalyticsEvents.GallerySaveCompleted, AnalyticsParams.Success, s.Success ? "true" : "false"); // 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.DrawingSaved, new Dictionary<string, object> _analytics.LogEvent(AnalyticsEvents.DrawingSaved, new Dictionary<string, object>
{ {
[AnalyticsParams.DrawingId] = _activeDrawingId ?? string.Empty, [AnalyticsParams.DrawingId] = drawingId,
[AnalyticsParams.Success] = s.Success ? 1 : 0, [AnalyticsParams.Success] = s.Success ? 1 : 0,
}); });
} }
@@ -191,6 +204,15 @@ namespace Darkmatter.Services.Analytics
_analytics.LogEvent(AnalyticsEvents.MainMenuReturned); _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 // 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. // reaching here with a non-null id means the drawing was left without finishing — the leak signal.
private void EndActiveDrawingIfAbandoned() private void EndActiveDrawingIfAbandoned()

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

@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
using Cysharp.Threading.Tasks; using Cysharp.Threading.Tasks;
@@ -5,6 +6,7 @@ using Darkmatter.Core.Contracts.Services.Camera;
using Darkmatter.Core.Contracts.Services.Capture; using Darkmatter.Core.Contracts.Services.Capture;
using UnityEngine; using UnityEngine;
using CameraType = Darkmatter.Core.Enums.Services.Camera.CameraType; using CameraType = Darkmatter.Core.Enums.Services.Camera.CameraType;
using Object = UnityEngine.Object;
namespace Darkmatter.Services.Capture namespace Darkmatter.Services.Capture
{ {
@@ -15,7 +17,7 @@ namespace Darkmatter.Services.Capture
public CaptureService(ICameraService cameraService) => _cameraService = cameraService; public CaptureService(ICameraService cameraService) => _cameraService = cameraService;
public async UniTask<byte[]> CapturePngAsync(GameObject captureObject, float scale, public async UniTask<byte[]> CapturePngAsync(GameObject captureObject, float scale,
CancellationToken cancellationToken = default) Func<IDisposable> captureViewScopeFactory = null, CancellationToken cancellationToken = default)
{ {
if (captureObject == null) return null; if (captureObject == null) return null;
var paperRT = captureObject.transform as RectTransform; var paperRT = captureObject.transform as RectTransform;
@@ -26,10 +28,6 @@ namespace Darkmatter.Services.Capture
int sw = Screen.width; int sw = Screen.width;
int sh = Screen.height; int sh = Screen.height;
Rect crop = ComputeCropRect(captureObject, sw, sh);
int cropW = Mathf.Max(1, (int)crop.width);
int cropH = Mathf.Max(1, (int)crop.height);
var prevFlags = cam.clearFlags; var prevFlags = cam.clearFlags;
var prevBg = cam.backgroundColor; var prevBg = cam.backgroundColor;
var prevTarget = cam.targetTexture; var prevTarget = cam.targetTexture;
@@ -41,15 +39,22 @@ namespace Darkmatter.Services.Capture
List<Canvas> disabledCanvases = null; List<Canvas> disabledCanvases = null;
List<UnityEngine.UI.Graphic> disabledGraphics = null; List<UnityEngine.UI.Graphic> disabledGraphics = null;
IDisposable captureViewScope = null;
try try
{ {
await UniTask.WaitForEndOfFrame(cancellationToken); await UniTask.WaitForEndOfFrame(cancellationToken);
captureViewScope = captureViewScopeFactory?.Invoke();
disabledCanvases = DisableOtherRootCanvases(paperCanvas); disabledCanvases = DisableOtherRootCanvases(paperCanvas);
disabledGraphics = HideNonPaperGraphics(paperRT); disabledGraphics = HideNonPaperGraphics(paperRT);
HideBackdropGraphics(paperRT, disabledGraphics); HideBackdropGraphics(paperRT, disabledGraphics);
Rect crop = ComputeCropRect(captureObject, sw, sh);
int cropW = Mathf.Max(1, (int)crop.width);
int cropH = Mathf.Max(1, (int)crop.height);
cam.clearFlags = CameraClearFlags.SolidColor; cam.clearFlags = CameraClearFlags.SolidColor;
cam.backgroundColor = new Color(0f, 0f, 0f, 0f); cam.backgroundColor = new Color(0f, 0f, 0f, 0f);
cam.targetTexture = rt; cam.targetTexture = rt;
@@ -63,52 +68,58 @@ namespace Darkmatter.Services.Capture
var prevActive = RenderTexture.active; var prevActive = RenderTexture.active;
RenderTexture.active = rt; RenderTexture.active = rt;
var fullScreen = new Texture2D(sw, sh, TextureFormat.RGBA32, mipChain: false); // Read only the crop region straight off the RT. A full-screen ReadPixels followed by
fullScreen.ReadPixels(new Rect(0, 0, sw, sh), 0, 0); // GetPixels/SetPixels cropping allocated a managed Color[] at 16 bytes/pixel (tens of MB)
fullScreen.Apply(); // on every capture — autosave runs this after each paint, OOM-killing low-RAM devices.
RenderTexture.active = prevActive; var cropped = new Texture2D(cropW, cropH, TextureFormat.RGBA32, mipChain: false);
try try
{ {
var cropped = new Texture2D(cropW, cropH, TextureFormat.RGBA32, mipChain: false); 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);
try try
{ {
cropped.SetPixels(fullScreen.GetPixels((int)crop.x, (int)crop.y, cropW, cropH)); return output.EncodeToPNG();
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 finally
{ {
Object.Destroy(cropped); if (output != cropped) Object.Destroy(output);
} }
} }
finally finally
{ {
Object.Destroy(fullScreen); RenderTexture.active = prevActive;
Object.Destroy(cropped);
} }
} }
finally finally
{ {
paperCanvas.renderMode = prevMode; // The awaits above give scene teardown (autosave racing an unload) a chance to
paperCanvas.worldCamera = prevWorldCam; // destroy the canvas or camera; touching them then would throw from finally and
paperCanvas.planeDistance = prevPlaneDist; // skip the rest of the restore.
cam.targetTexture = prevTarget; if (paperCanvas != null)
cam.clearFlags = prevFlags; {
cam.backgroundColor = prevBg; paperCanvas.renderMode = prevMode;
paperCanvas.worldCamera = prevWorldCam;
paperCanvas.planeDistance = prevPlaneDist;
}
if (cam != null)
{
cam.targetTexture = prevTarget;
cam.clearFlags = prevFlags;
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)
@@ -118,6 +129,7 @@ namespace Darkmatter.Services.Capture
foreach (var c in disabledCanvases) foreach (var c in disabledCanvases)
if (c != null) if (c != null)
c.enabled = true; c.enabled = true;
captureViewScope?.Dispose();
} }
} }
@@ -217,4 +229,4 @@ namespace Darkmatter.Services.Capture
} }
} }
} }
} }

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);
output.ReadPixels(new Rect(0, 0, bgW, bgH), 0, 0); try
output.Apply(); {
output.ReadPixels(new Rect(0, 0, bgW, bgH), 0, 0);
output.Apply();
}
catch
{
UnityEngine.Object.Destroy(output);
throw;
}
return output; return output;
} }
finally finally

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

@@ -0,0 +1,234 @@
fileFormatVersion: 2
guid: 1951198dacf07e54dbed73ea007ea12e
TextureImporter:
internalIDToNameTable:
- first:
213: -8051739824182752894
second: BottomWingsCircle_0
- first:
213: -2728466869657715014
second: BottomWingsCircle_1
- first:
213: 8546343078113801825
second: BottomWingsCircle_2
- first:
213: -4528582518283939828
second: BottomWingsCircle_3
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 1
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites:
- serializedVersion: 2
name: BottomWingsCircle_0
rect:
serializedVersion: 2
x: 0
y: 0
width: 66
height: 65
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 28988b33a49724090800000000000000
internalID: -8051739824182752894
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: BottomWingsCircle_1
rect:
serializedVersion: 2
x: 108
y: 13
width: 65
height: 66
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: abe31ac86d9822ad0800000000000000
internalID: -2728466869657715014
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: BottomWingsCircle_2
rect:
serializedVersion: 2
x: 594
y: 13
width: 66
height: 66
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 1667793cfb5ba9670800000000000000
internalID: 8546343078113801825
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: BottomWingsCircle_3
rect:
serializedVersion: 2
x: 702
y: 0
width: 65
height: 65
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: c0054ad613e3721c0800000000000000
internalID: -4528582518283939828
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable:
BottomWingsCircle_0: -8051739824182752894
BottomWingsCircle_1: -2728466869657715014
BottomWingsCircle_2: 8546343078113801825
BottomWingsCircle_3: -4528582518283939828
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1,182 @@
fileFormatVersion: 2
guid: 9d317f0177e1dbc45ae1809ba1a5c789
TextureImporter:
internalIDToNameTable:
- first:
213: -4850049984293518407
second: BusLight_0
- first:
213: -5093069627646189050
second: BusLight_1
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 1
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites:
- serializedVersion: 2
name: BusLight_0
rect:
serializedVersion: 2
x: 0
y: 76
width: 653
height: 43
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 9b3cb476c3921bcb0800000000000000
internalID: -4850049984293518407
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: BusLight_1
rect:
serializedVersion: 2
x: 863
y: 0
width: 30
height: 41
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 60261db6828c159b0800000000000000
internalID: -5093069627646189050
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable:
BusLight_0: -4850049984293518407
BusLight_1: -5093069627646189050
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,182 @@
fileFormatVersion: 2
guid: ddad10803fb8c2c429d2fa9e9f3151b4
TextureImporter:
internalIDToNameTable:
- first:
213: 1896717349252663608
second: BusTire_0
- first:
213: -2174636688557857145
second: BusTire_1
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 1
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites:
- serializedVersion: 2
name: BusTire_0
rect:
serializedVersion: 2
x: 0
y: 0
width: 180
height: 178
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 8398681416e725a10800000000000000
internalID: 1896717349252663608
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: BusTire_1
rect:
serializedVersion: 2
x: 505
y: 0
width: 180
height: 178
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 7862af6147322d1e0800000000000000
internalID: -2174636688557857145
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable:
BusTire_0: 1896717349252663608
BusTire_1: -2174636688557857145
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -0,0 +1,260 @@
fileFormatVersion: 2
guid: f758d9db002cfcb4f93438996861a3cb
TextureImporter:
internalIDToNameTable:
- first:
213: 6471150036080952054
second: ButterflyBody_0
- first:
213: -6321697714792679525
second: ButterflyBody_1
- first:
213: -6691350468167394780
second: ButterflyBody_2
- first:
213: 6673823329618462000
second: ButterflyBody_3
- first:
213: -217602427717472224
second: ButterflyBody_4
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 1
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites:
- serializedVersion: 2
name: ButterflyBody_0
rect:
serializedVersion: 2
x: 2
y: 321
width: 129
height: 162
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 6f28cd41ca42ec950800000000000000
internalID: 6471150036080952054
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: ButterflyBody_1
rect:
serializedVersion: 2
x: 2
y: 237
width: 127
height: 97
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: b9732641c61d448a0800000000000000
internalID: -6321697714792679525
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: ButterflyBody_2
rect:
serializedVersion: 2
x: 0
y: 151
width: 131
height: 99
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 42a1d35933c8323a0800000000000000
internalID: -6691350468167394780
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: ButterflyBody_3
rect:
serializedVersion: 2
x: 2
y: 70
width: 127
height: 94
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 0358f9775fe2e9c50800000000000000
internalID: 6673823329618462000
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: ButterflyBody_4
rect:
serializedVersion: 2
x: 17
y: 0
width: 97
height: 80
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 02033a1bebbeafcf0800000000000000
internalID: -217602427717472224
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable:
ButterflyBody_0: 6471150036080952054
ButterflyBody_1: -6321697714792679525
ButterflyBody_2: -6691350468167394780
ButterflyBody_3: 6673823329618462000
ButterflyBody_4: -217602427717472224
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -0,0 +1,208 @@
fileFormatVersion: 2
guid: 88e500f0c8c676544818a526667b4928
TextureImporter:
internalIDToNameTable:
- first:
213: -3688669185434742539
second: CarBumper_0
- first:
213: -5122869874224342635
second: CarBumper_1
- first:
213: -2230590542832800911
second: CarBumper_2
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 1
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites:
- serializedVersion: 2
name: CarBumper_0
rect:
serializedVersion: 2
x: 0
y: 0
width: 121
height: 75
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 5f09b345be63fccc0800000000000000
internalID: -3688669185434742539
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: CarBumper_1
rect:
serializedVersion: 2
x: 346
y: 0
width: 313
height: 75
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 599de44ddf8e7e8b0800000000000000
internalID: -5122869874224342635
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: CarBumper_2
rect:
serializedVersion: 2
x: 881
y: 0
width: 121
height: 75
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 17bea1f28b95b01e0800000000000000
internalID: -2230590542832800911
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable:
CarBumper_0: -3688669185434742539
CarBumper_1: -5122869874224342635
CarBumper_2: -2230590542832800911
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@@ -0,0 +1,182 @@
fileFormatVersion: 2
guid: e6dae4dd201451b4f8ec72d21a12c942
TextureImporter:
internalIDToNameTable:
- first:
213: -3762444342573962785
second: CarGlass_0
- first:
213: -3394847969756668567
second: CarGlass_1
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 1
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites:
- serializedVersion: 2
name: CarGlass_0
rect:
serializedVersion: 2
x: 0
y: 0
width: 218
height: 156
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: fd963b10ecc19cbc0800000000000000
internalID: -3762444342573962785
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: CarGlass_1
rect:
serializedVersion: 2
x: 254
y: 0
width: 309
height: 156
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 96d923812c313e0d0800000000000000
internalID: -3394847969756668567
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable:
CarGlass_0: -3762444342573962785
CarGlass_1: -3394847969756668567
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@@ -0,0 +1,182 @@
fileFormatVersion: 2
guid: ad24addbac9032c469f76321a56d672d
TextureImporter:
internalIDToNameTable:
- first:
213: -655066742942654475
second: CarInTire_0
- first:
213: -1498608758231420081
second: CarInTire_1
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 1
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites:
- serializedVersion: 2
name: CarInTire_0
rect:
serializedVersion: 2
x: 0
y: 0
width: 124
height: 124
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 5ff1bba944cb8e6f0800000000000000
internalID: -655066742942654475
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: CarInTire_1
rect:
serializedVersion: 2
x: 284
y: 0
width: 124
height: 124
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: f43b35c364fd33be0800000000000000
internalID: -1498608758231420081
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable:
CarInTire_0: -655066742942654475
CarInTire_1: -1498608758231420081
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,182 @@
fileFormatVersion: 2
guid: a9267392a620fcc499e5ecfb4b0a917f
TextureImporter:
internalIDToNameTable:
- first:
213: -7769440039808934438
second: CarOutTire_0
- first:
213: 5100897469767027331
second: CarOutTire_1
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 1
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites:
- serializedVersion: 2
name: CarOutTire_0
rect:
serializedVersion: 2
x: 0
y: 0
width: 234
height: 234
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: adddd73b2776d2490800000000000000
internalID: -7769440039808934438
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: CarOutTire_1
rect:
serializedVersion: 2
x: 539
y: 0
width: 234
height: 234
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 38a072919370ac640800000000000000
internalID: 5100897469767027331
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable:
CarOutTire_0: -7769440039808934438
CarOutTire_1: 5100897469767027331
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

View File

@@ -0,0 +1,156 @@
fileFormatVersion: 2
guid: 46ad2bd15abf86e45a975ec3a758bb53
TextureImporter:
internalIDToNameTable:
- first:
213: 6370930181638868744
second: CatBody_0
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 1
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites:
- serializedVersion: 2
name: CatBody_0
rect:
serializedVersion: 2
x: 0
y: 0
width: 1307
height: 922
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 80f466dde371a6850800000000000000
internalID: 6370930181638868744
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable:
CatBody_0: 6370930181638868744
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

View File

@@ -0,0 +1,286 @@
fileFormatVersion: 2
guid: c3c0dd341f8436a429f851f9cb10a693
TextureImporter:
internalIDToNameTable:
- first:
213: 854360137450151800
second: ChuraBody_0
- first:
213: 300892820482043386
second: ChuraBody_1
- first:
213: -7596675443362104391
second: ChuraBody_2
- first:
213: -5812798879745435036
second: ChuraBody_3
- first:
213: -2230349933545200123
second: ChuraBody_4
- first:
213: -1225461193530744214
second: ChuraBody_5
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 1
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites:
- serializedVersion: 2
name: ChuraBody_0
rect:
serializedVersion: 2
x: 0
y: 194
width: 31
height: 40
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 87f58f6b40c4bdb00800000000000000
internalID: 854360137450151800
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: ChuraBody_1
rect:
serializedVersion: 2
x: 3
y: 93
width: 982
height: 231
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: afd2c401d6cfc2400800000000000000
internalID: 300892820482043386
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: ChuraBody_2
rect:
serializedVersion: 2
x: 18
y: 189
width: 878
height: 284
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 9bbf774eeef239690800000000000000
internalID: -7596675443362104391
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: ChuraBody_3
rect:
serializedVersion: 2
x: 533
y: 79
width: 847
height: 667
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 46a1cf5673ac45fa0800000000000000
internalID: -5812798879745435036
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: ChuraBody_4
rect:
serializedVersion: 2
x: 620
y: 214
width: 760
height: 539
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 506e8366d843c01e0800000000000000
internalID: -2230349933545200123
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: ChuraBody_5
rect:
serializedVersion: 2
x: 28
y: 0
width: 1015
height: 200
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: a62e29989894efee0800000000000000
internalID: -1225461193530744214
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable:
ChuraBody_0: 854360137450151800
ChuraBody_1: 300892820482043386
ChuraBody_2: -7596675443362104391
ChuraBody_3: -5812798879745435036
ChuraBody_4: -2230349933545200123
ChuraBody_5: -1225461193530744214
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

View File

@@ -0,0 +1,312 @@
fileFormatVersion: 2
guid: dc0b930c771985d4691cb92ab59f279b
TextureImporter:
internalIDToNameTable:
- first:
213: 3837443025948308975
second: DanfeBeak_0
- first:
213: 511470280823894669
second: DanfeBeak_1
- first:
213: 1166802717158667554
second: DanfeBeak_2
- first:
213: -6432545699801005218
second: DanfeBeak_3
- first:
213: 102218829908077718
second: DanfeBeak_4
- first:
213: -5383097860671483926
second: DanfeBeak_5
- first:
213: -8545532576490953119
second: DanfeBeak_6
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 1
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites:
- serializedVersion: 2
name: DanfeBeak_0
rect:
serializedVersion: 2
x: 0
y: 809
width: 83
height: 42
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: fe5f277b916514530800000000000000
internalID: 3837443025948308975
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: DanfeBeak_1
rect:
serializedVersion: 2
x: 108
y: 21
width: 21
height: 19
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: d827da1b28b191700800000000000000
internalID: 511470280823894669
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: DanfeBeak_2
rect:
serializedVersion: 2
x: 152
y: 7
width: 20
height: 18
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 225f337a3e0513010800000000000000
internalID: 1166802717158667554
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: DanfeBeak_3
rect:
serializedVersion: 2
x: 231
y: 22
width: 14
height: 17
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: e5305e164c10bb6a0800000000000000
internalID: -6432545699801005218
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: DanfeBeak_4
rect:
serializedVersion: 2
x: 255
y: 18
width: 16
height: 17
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 69ce606fb772b6100800000000000000
internalID: 102218829908077718
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: DanfeBeak_5
rect:
serializedVersion: 2
x: 369
y: 23
width: 15
height: 18
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: aefc28f34056b45b0800000000000000
internalID: -5383097860671483926
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: DanfeBeak_6
rect:
serializedVersion: 2
x: 273
y: 0
width: 17
height: 18
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 1666ab9d56b286980800000000000000
internalID: -8545532576490953119
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable:
DanfeBeak_0: 3837443025948308975
DanfeBeak_1: 511470280823894669
DanfeBeak_2: 1166802717158667554
DanfeBeak_3: -6432545699801005218
DanfeBeak_4: 102218829908077718
DanfeBeak_5: -5383097860671483926
DanfeBeak_6: -8545532576490953119
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -0,0 +1,234 @@
fileFormatVersion: 2
guid: 4b8517249a6f5fb46ad924beca0bc0ed
TextureImporter:
internalIDToNameTable:
- first:
213: 1251703262395139982
second: DanfeHeadPart_0
- first:
213: 4276034837665305358
second: DanfeHeadPart_1
- first:
213: 6311362011549722110
second: DanfeHeadPart_2
- first:
213: 8900275555671599677
second: DanfeHeadPart_3
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 1
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites:
- serializedVersion: 2
name: DanfeHeadPart_0
rect:
serializedVersion: 2
x: 0
y: 0
width: 306
height: 760
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: e87824f3c71fe5110800000000000000
internalID: 1251703262395139982
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: DanfeHeadPart_1
rect:
serializedVersion: 2
x: 90
y: 760
width: 65
height: 104
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: e035c3a8707875b30800000000000000
internalID: 4276034837665305358
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: DanfeHeadPart_2
rect:
serializedVersion: 2
x: 109
y: 760
width: 66
height: 86
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: ef5e05b7f46769750800000000000000
internalID: 6311362011549722110
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: DanfeHeadPart_3
rect:
serializedVersion: 2
x: 252
y: 2
width: 71
height: 57
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: d3ae5e25671248b70800000000000000
internalID: 8900275555671599677
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable:
DanfeHeadPart_0: 1251703262395139982
DanfeHeadPart_1: 4276034837665305358
DanfeHeadPart_2: 6311362011549722110
DanfeHeadPart_3: 8900275555671599677
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -0,0 +1,156 @@
fileFormatVersion: 2
guid: c2feb026e2f604342ad6fb1d74592cc0
TextureImporter:
internalIDToNameTable:
- first:
213: -8558426319100075322
second: DanfeWings1_0
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 1
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites:
- serializedVersion: 2
name: DanfeWings1_0
rect:
serializedVersion: 2
x: 0
y: 0
width: 370
height: 360
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 6c2afa57b9c5a3980800000000000000
internalID: -8558426319100075322
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable:
DanfeWings1_0: -8558426319100075322
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -0,0 +1,182 @@
fileFormatVersion: 2
guid: 499c759e688e46544a815a18284fb9f0
TextureImporter:
internalIDToNameTable:
- first:
213: 4129204393604476333
second: DanfeWings2_0
- first:
213: -1500505832241293476
second: DanfeWings2_1
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 1
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites:
- serializedVersion: 2
name: DanfeWings2_0
rect:
serializedVersion: 2
x: 0
y: 33
width: 454
height: 591
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: dade6cdb481ed4930800000000000000
internalID: 4129204393604476333
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: DanfeWings2_1
rect:
serializedVersion: 2
x: 430
y: 0
width: 30
height: 46
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: c571bd545e12d2be0800000000000000
internalID: -1500505832241293476
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable:
DanfeWings2_0: 4129204393604476333
DanfeWings2_1: -1500505832241293476
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

@@ -0,0 +1,468 @@
fileFormatVersion: 2
guid: 53775cb35b0569d4280736de13a98589
TextureImporter:
internalIDToNameTable:
- first:
213: 5033249586431198743
second: DashainPingGirl_0
- first:
213: 8324659869264839602
second: DashainPingGirl_1
- first:
213: 3666975675418248262
second: DashainPingGirl_2
- first:
213: -3184958953187423986
second: DashainPingGirl_3
- first:
213: -3250004282446318388
second: DashainPingGirl_4
- first:
213: -7112137682081183080
second: DashainPingGirl_5
- first:
213: 5843740985476730191
second: DashainPingGirl_6
- first:
213: 7411241082712134665
second: DashainPingGirl_7
- first:
213: 2234119483297400307
second: DashainPingGirl_8
- first:
213: 2474659348792208325
second: DashainPingGirl_9
- first:
213: -8755734003182348925
second: DashainPingGirl_10
- first:
213: 1407992175052348958
second: DashainPingGirl_11
- first:
213: -4908947008148759518
second: DashainPingGirl_12
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 1
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites:
- serializedVersion: 2
name: DashainPingGirl_0
rect:
serializedVersion: 2
x: 29
y: 192
width: 8
height: 16
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 7160d11e4d1b9d540800000000000000
internalID: 5033249586431198743
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: DashainPingGirl_1
rect:
serializedVersion: 2
x: 29
y: 177
width: 13
height: 15
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 2b30b7f8b02278370800000000000000
internalID: 8324659869264839602
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: DashainPingGirl_2
rect:
serializedVersion: 2
x: 33
y: 172
width: 71
height: 74
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 6448d45c2f6b3e230800000000000000
internalID: 3666975675418248262
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: DashainPingGirl_3
rect:
serializedVersion: 2
x: 100
y: 192
width: 9
height: 16
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: e09401be4b0ccc3d0800000000000000
internalID: -3184958953187423986
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: DashainPingGirl_4
rect:
serializedVersion: 2
x: 0
y: 161
width: 15
height: 24
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: cc4f94a035aa5e2d0800000000000000
internalID: -3250004282446318388
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: DashainPingGirl_5
rect:
serializedVersion: 2
x: 95
y: 177
width: 14
height: 14
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 89a81e9b57c9c4d90800000000000000
internalID: -7112137682081183080
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: DashainPingGirl_6
rect:
serializedVersion: 2
x: 119
y: 161
width: 15
height: 24
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: f4d4bbef673291150800000000000000
internalID: 5843740985476730191
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: DashainPingGirl_7
rect:
serializedVersion: 2
x: 13
y: 113
width: 109
height: 62
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 90496df8f740ad660800000000000000
internalID: 7411241082712134665
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: DashainPingGirl_8
rect:
serializedVersion: 2
x: 19
y: 60
width: 99
height: 56
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 3f588bb45df210f10800000000000000
internalID: 2234119483297400307
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: DashainPingGirl_9
rect:
serializedVersion: 2
x: 25
y: 0
width: 30
height: 25
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 5cbebd00191c75220800000000000000
internalID: 2474659348792208325
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: DashainPingGirl_10
rect:
serializedVersion: 2
x: 28
y: 24
width: 29
height: 40
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 385d18060526d7680800000000000000
internalID: -8755734003182348925
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: DashainPingGirl_11
rect:
serializedVersion: 2
x: 79
y: 23
width: 28
height: 40
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: e1e56806c613a8310800000000000000
internalID: 1407992175052348958
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: DashainPingGirl_12
rect:
serializedVersion: 2
x: 80
y: 0
width: 31
height: 25
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 22c931e64baefdbb0800000000000000
internalID: -4908947008148759518
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable:
DashainPingGirl_0: 5033249586431198743
DashainPingGirl_1: 8324659869264839602
DashainPingGirl_10: -8755734003182348925
DashainPingGirl_11: 1407992175052348958
DashainPingGirl_12: -4908947008148759518
DashainPingGirl_2: 3666975675418248262
DashainPingGirl_3: -3184958953187423986
DashainPingGirl_4: -3250004282446318388
DashainPingGirl_5: -7112137682081183080
DashainPingGirl_6: 5843740985476730191
DashainPingGirl_7: 7411241082712134665
DashainPingGirl_8: 2234119483297400307
DashainPingGirl_9: 2474659348792208325
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

View File

@@ -0,0 +1,208 @@
fileFormatVersion: 2
guid: a67fcc28ba300164d8941f6e9af6c990
TextureImporter:
internalIDToNameTable:
- first:
213: 3965620083542346913
second: DiyoBody_0
- first:
213: 2310740630014503113
second: DiyoBody_1
- first:
213: -8515455852279263476
second: DiyoBody_2
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 1
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites:
- serializedVersion: 2
name: DiyoBody_0
rect:
serializedVersion: 2
x: 0
y: 215
width: 1114
height: 453
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 1ac42b98476b80730800000000000000
internalID: 3965620083542346913
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: DiyoBody_1
rect:
serializedVersion: 2
x: 64
y: 42
width: 876
height: 293
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 9c8a3143c56611020800000000000000
internalID: 2310740630014503113
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: DiyoBody_2
rect:
serializedVersion: 2
x: 319
y: 0
width: 399
height: 82
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: c0f8f58e40603d980800000000000000
internalID: -8515455852279263476
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable:
DiyoBody_0: 3965620083542346913
DiyoBody_1: 2310740630014503113
DiyoBody_2: -8515455852279263476
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -0,0 +1,182 @@
fileFormatVersion: 2
guid: 1f62c31c92fb3854d8267335453868a7
TextureImporter:
internalIDToNameTable:
- first:
213: 4842108114577440398
second: DiyoFlame_0
- first:
213: -5088445411688168677
second: DiyoFlame_1
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 1
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites:
- serializedVersion: 2
name: DiyoFlame_0
rect:
serializedVersion: 2
x: 0
y: 5
width: 170
height: 444
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: e8ebc65fcaf923340800000000000000
internalID: 4842108114577440398
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: DiyoFlame_1
rect:
serializedVersion: 2
x: 48
y: 0
width: 84
height: 200
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: b134c599bd53269b0800000000000000
internalID: -5088445411688168677
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable:
DiyoFlame_0: 4842108114577440398
DiyoFlame_1: -5088445411688168677
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

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