Compare commits

...

8 Commits

Author SHA1 Message Date
Mausham
e3f047de2b Merge remote-tracking branch 'origin/savya' into work_branch 2026-06-03 10:54:02 +05:45
Savya Bikram Shah
0bfcfa3038 Tutorial done 2026-06-03 10:53:45 +05:45
Mausham
b4e0cb0530 Merge remote-tracking branch 'origin/savya' into work_branch
# Conflicts:
#	Assets/Darkmatter/Content/Fonts/static/Fredoka-Bold SDF Blue Outline.asset
2026-06-03 10:24:03 +05:45
Savya Bikram Shah
f81a17e1f5 fix 2026-06-02 13:06:40 +05:45
Savya Bikram Shah
64b383fe6e fixes 2026-06-02 13:03:27 +05:45
Savya Bikram Shah
46b5bcc978 fixes 2026-06-02 12:48:55 +05:45
Savya Bikram Shah
28f431f26a test ads removed 2026-06-01 18:16:34 +05:45
Savya Bikram Shah
d3cc23a1ce NEver sleeps 2026-06-01 18:04:28 +05:45
127 changed files with 9457 additions and 5443 deletions

BIN
.DS_Store vendored

Binary file not shown.

7
.idea/.idea.Colorbook/.idea/discord.xml generated Normal file
View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DiscordProjectSettings">
<option name="show" value="PROJECT_FILES" />
<option name="description" value="" />
</component>
</project>

View File

@@ -5,7 +5,6 @@
<androidPackage spec="com.appsflyer:af-android-sdk:6.17.6"></androidPackage>
<androidPackage spec="com.appsflyer:unity-wrapper:6.17.91"></androidPackage>
<androidPackage spec="com.android.installreferrer:installreferrer:2.1"></androidPackage>
<androidPackage spec="com.appsflyer:purchase-connector:2.2.0"></androidPackage>
</androidPackages>
<iosPods>

View File

@@ -14,6 +14,11 @@ public interface IColoringController
void PaintRegion(string regionId, Color color);
IReadOnlyDictionary<string, Color> GetCurrentColors();
UniTask PlayCompletionAnimationAsync(CancellationToken ct);
// True while the completion celebration is on screen (the real drawing is hidden).
// Capture must skip these frames so it never grabs the animation instead of the art.
bool IsPlayingCompletionAnimation { get; }
bool HasNonAuthoredColors { get; }
void ResetAll();
void Clear();

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: d63726aaa9398f341a1a923bc039c446
guid: fac6898f446df4a8e994fa47eed10068
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@@ -0,0 +1,14 @@
namespace Darkmatter.Core.Contracts.Features.Tutorial
{
/// <summary>
/// Decides whether the one-time tutorial should run, and records that it has been completed.
/// </summary>
public interface ITutorialGate
{
/// <summary>True only for a genuine first-time player who has not finished the tutorial.</summary>
bool ShouldRun { get; }
/// <summary>Persist that the tutorial is done so it never runs again.</summary>
void MarkCompleted();
}
}

View File

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

View File

@@ -0,0 +1,36 @@
using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine;
namespace Darkmatter.Core.Contracts.Features.Tutorial
{
/// <summary>
/// Full-screen guidance overlay: a dim frame with a cut-out "hole" over a target element,
/// an animated hand + halo, and an instruction bubble. Lives on a persistent root-scoped
/// Canvas so it survives scene swaps (mirrors the loading screen).
/// </summary>
public interface ITutorialOverlay
{
/// <summary>True once the view is initialised and safe to drive.</summary>
bool IsReady { get; }
/// <summary>
/// Spotlight a single tap target. When <paramref name="target"/> is null the dim/hole are
/// skipped and only a centered bubble is shown (a non-blocking hint).
/// When <paramref name="blockInput"/> is true every touch outside the hole is swallowed.
/// </summary>
void ShowTap(RectTransform target, string message, bool blockInput);
/// <summary>
/// Spotlight a drag gesture from <paramref name="from"/> to <paramref name="to"/>. Input is
/// never blocked here, so the dragged piece can render above the dim.
/// </summary>
void ShowDrag(RectTransform from, RectTransform to, string message);
/// <summary>Hide everything immediately (used across scene swaps).</summary>
void HideInstant();
/// <summary>Show a centered celebratory bubble for a short beat, then hide.</summary>
UniTask ShowToastAsync(string message, CancellationToken ct);
}
}

View File

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

View File

@@ -0,0 +1,6 @@
using UnityEngine;
namespace Darkmatter.Core.Data.Signals.Features.Coloring
{
public record struct ColorSelectedSignal(int Index, Color Color);
}

View File

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

View File

@@ -0,0 +1,6 @@
namespace Darkmatter.Core
{
// Published by the catalog presenter once the drawing grid is populated and on screen.
// Namespace matches the sibling OpenColorBookSignal so existing catalog code needs no new using.
public record struct DrawingCatalogReadySignal;
}

View File

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

View File

@@ -1,5 +1,6 @@
fileFormatVersion: 2
guid: 8f948c844c48b42479fbc2aea5142bc1
guid: 3aee861659cfe43c4b48d513479c2d7d
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:

View File

@@ -0,0 +1,4 @@
namespace Darkmatter.Core.Data.Signals.Features.Tutorial
{
public record struct TutorialCompletedSignal;
}

View File

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

View File

@@ -0,0 +1,4 @@
namespace Darkmatter.Core.Data.Signals.Features.Tutorial
{
public record struct TutorialStartedSignal;
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 3aeb975bb1d6a45aca5c86ddf72bee22

View File

@@ -0,0 +1,4 @@
namespace Darkmatter.Core.Data.Signals.Features.Tutorial
{
public record struct TutorialStepCompletedSignal(string StepId, int StepIndex);
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 11b2e1b23836f4664be3a84389c1c364

View File

@@ -16,6 +16,7 @@ namespace Darkmatter.Core.Data.Static.Features.ShapeBuilder
[Header("Drag")]
[SerializeField, Range(1f, 2f)] private float dragScale = 1.15f;
[SerializeField, Range(0f, 1f)] private float dragAlpha = 0.7f;
[Header("Preview easing")]
[SerializeField] private AnimationCurve previewCurve = AnimationCurve.EaseInOut(0, 0, 1, 1);
@@ -25,6 +26,7 @@ namespace Darkmatter.Core.Data.Static.Features.ShapeBuilder
public float SnapDuration => snapDuration;
public float ReturnDuration => returnDuration;
public float DragScale => dragScale;
public float DragAlpha => dragAlpha;
public AnimationCurve PreviewCurve => previewCurve;
public Vector2 DragSizeDelta(ShapeSO shape) =>

View File

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

View File

@@ -0,0 +1,43 @@
using UnityEngine;
namespace Darkmatter.Core.Data.Static.Features.Tutorial
{
/// <summary>
/// Designer-tunable copy and timing for the one-time onboarding tutorial. The step *sequence*
/// is fixed in TutorialDirector; only the wording, the spotlight padding and the per-step
/// watchdog timeout live here.
/// </summary>
[CreateAssetMenu(fileName = "TutorialStepsConfig",
menuName = "Darkmatter/Tutorial/Steps Config")]
public sealed class TutorialStepsConfig : ScriptableObject
{
[Header("Step copy (kept short for young readers)")]
[SerializeField] private string pickText = "Pick a picture to start!";
[SerializeField] private string dragText = "Drag the piece to its spot!";
[SerializeField] private string finishText = "Now finish the puzzle!";
[SerializeField] private string colorText = "Choose a color!";
[SerializeField] private string paintText = "Tap the picture to color it!";
[SerializeField] private string nextText = "Tap Next when you're done!";
[SerializeField] private string doneText = "Yay! You did it!";
[Header("Watchdog")]
[Tooltip("Timeout (s) while waiting for a SYSTEM precondition (scene/catalog/regions ready). If it " +
"doesn't arrive the tutorial fails open so the player is never trapped.")]
[SerializeField] private float stepTimeoutSeconds = 45f;
[Tooltip("Timeout (s) while waiting for the CHILD's own action (drag/tap/paint). 0 = no timeout: " +
"the hint stays until they do it. Set a large value if you want a safety net instead.")]
[SerializeField] private float actionTimeoutSeconds;
public string PickText => pickText;
public string DragText => dragText;
public string FinishText => finishText;
public string ColorText => colorText;
public string PaintText => paintText;
public string NextText => nextText;
public string DoneText => doneText;
public float StepTimeoutSeconds => stepTimeoutSeconds;
public float ActionTimeoutSeconds => actionTimeoutSeconds;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 5c513430cc85f4357b3df1da019bf554

View File

@@ -30,6 +30,7 @@ namespace Darkmatter.Features.AppBoot.Flow
public async UniTask StartAsync(CancellationToken cancellation = default)
{
Screen.sleepTimeout = SleepTimeout.NeverSleep;
await _progression.LoadAsync();
var tcs = new UniTaskCompletionSource();

View File

@@ -1,6 +1,7 @@
using System.Threading;
using Cysharp.Threading.Tasks;
using Darkmatter.Core.Contracts.Features.Capture;
using Darkmatter.Core.Contracts.Features.Coloring;
using Darkmatter.Core.Contracts.Features.DrawingCatalog;
using Darkmatter.Core.Contracts.Features.GameplayFlow;
using Darkmatter.Core.Contracts.Features.Progression;
@@ -21,6 +22,7 @@ namespace Darkmatter.Features.Capture
private readonly CaptureConfig _config;
private readonly IProgressionSystem _progression;
private readonly IDrawingTemplateCatalog _catalog;
private readonly IColoringController _coloring;
public CaptureSystem(
ICaptureService captureService,
@@ -29,7 +31,8 @@ namespace Darkmatter.Features.Capture
IEventBus bus,
CaptureConfig config,
IProgressionSystem progression,
IDrawingTemplateCatalog catalog)
IDrawingTemplateCatalog catalog,
IColoringController coloring)
{
_captureService = captureService;
_galleryService = galleryService;
@@ -38,10 +41,16 @@ namespace Darkmatter.Features.Capture
_config = config;
_progression = progression;
_catalog = catalog;
_coloring = coloring;
}
public async UniTask<byte[]> CapturePngAsync(bool saveToGallery = false, CancellationToken ct = default)
{
// The completion celebration hides the real drawing and shows an animation object
// under PaperRoot. Capturing then would grab the animation, not the art, so skip.
// A null result keeps the existing thumbnail and skips the gallery write (both no-op on null).
if (_coloring.IsPlayingCompletionAnimation) return null;
var png = await _captureService.CapturePngAsync(_refs.PaperRoot.gameObject, _config.CaptureScale, ct);
if (!saveToGallery || png == null || png.Length == 0) return png;

View File

@@ -50,12 +50,21 @@ public class ColorbookFlowController : IAsyncStartable, IDisposable
public async UniTask StartAsync(CancellationToken cancellation = new CancellationToken())
{
_scopeCts = CancellationTokenSource.CreateLinkedTokenSource(cancellation);
// Grab the token value up front. If the scope is torn down (scene unload) while the
// init below is in flight, the await resumes during disposal — when _scopeCts is already
// disposed and its .Token getter would throw ObjectDisposedException. The captured struct
// stays safe to read.
var ct = _scopeCts.Token;
_returnToMainMenuSubscription = _bus.Subscribe<ReturnToMainMenuSignal>(OnReturnToMainMenu);
_loadingScreen.SetProgress(1f);
await _drawingCatalog.InitializeAsync(cancellation);
await _drawingCatalog.InitializeAsync(ct);
// scope disposed mid-init; nothing left to do. Check the external token too — it's the
// one VContainer cancels on teardown, and the linked _scopeCts may lag the callback order.
if (cancellation.IsCancellationRequested || ct.IsCancellationRequested) return;
if (!_navigatingToGameplay) _loadingScreen.Hide();
PrewarmInterstitialAdAsync(_scopeCts.Token).Forget();
PrewarmInterstitialAdAsync(ct).Forget();
}
private async UniTaskVoid PrewarmInterstitialAdAsync(CancellationToken ct)

View File

@@ -33,6 +33,7 @@ public class ColoringController : IColoringController, IDisposable
private GameObject _colorButtonPrefab;
private GameObject _completionAnimationInstance;
private CompletionAnimationView _completionAnimationView;
private bool _isPlayingCompletionAnimation;
private readonly List<ColorRegionView> _regions = new();
private readonly List<ColorButton> _buttons = new();
private readonly Dictionary<string, Color> _authoredColors = new();
@@ -88,17 +89,21 @@ public class ColoringController : IColoringController, IDisposable
return snapshot;
}
public bool IsPlayingCompletionAnimation => _isPlayingCompletionAnimation;
public async UniTask PlayCompletionAnimationAsync(CancellationToken ct)
{
if (_completionAnimationInstance == null || _completionAnimationView == null) return;
if (_colorInstance != null) _colorInstance.SetActive(false);
_completionAnimationInstance.SetActive(true);
_isPlayingCompletionAnimation = true;
try
{
await _completionAnimationView.PlayAsync(ct);
}
finally
{
_isPlayingCompletionAnimation = false;
if (_completionAnimationInstance != null)
_completionAnimationInstance.SetActive(false);
}
@@ -133,6 +138,7 @@ public class ColoringController : IColoringController, IDisposable
public void Clear()
{
_history.Drop();
_isPlayingCompletionAnimation = false;
foreach (var button in _buttons)
if (button != null)

View File

@@ -1,11 +1,20 @@
using System;
using Darkmatter.Core.Contracts.Features.Coloring;
using Darkmatter.Core.Data.Signals.Features.Coloring;
using Darkmatter.Libs.Observer;
using UnityEngine;
namespace Darkmatter.Features.Coloring.Systems;
public class ColoringStateRepository
{
private readonly IEventBus _bus;
public ColoringStateRepository(IEventBus bus)
{
_bus = bus;
}
public IColorPalette SelectedPalette { get; private set; }
public int SelectedIndex { get; private set; }
public Color SelectedColor =>
@@ -29,5 +38,9 @@ public class ColoringStateRepository
if (idx < 0) return;
SelectedIndex = idx;
SelectedIndexChanged?.Invoke();
// Surface the user's colour pick on the bus so the tutorial (a root-scoped service that
// can't reach this scene-scoped repository directly) can detect it. Only fires on an actual
// SelectColor — not on the default selection made in SetPalette.
_bus.Publish(new ColorSelectedSignal(idx, color));
}
}

View File

@@ -122,6 +122,9 @@ namespace Darkmatter.Features.DrawingCatalog
// 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()

View File

@@ -136,6 +136,12 @@ namespace Darkmatter.Features.GameplayFlow.Systems
public async UniTask NextAsync(CancellationToken ct)
{
// Drop any debounced autosave so it can't fire during/after the completion
// animation and capture the animation (or an empty paper) into the thumbnail.
_autosaveCts?.Cancel();
_autosaveCts?.Dispose();
_autosaveCts = null;
await SaveCurrentAsync(ct);
_refs.Confetti.Play();
_sfx.Play(SfxId.FireWorkLaunch);

View File

@@ -36,6 +36,7 @@ namespace Darkmatter.Features.ShapeBuilder.UI
private Vector2 _origAnchorMax;
private Vector2 _origPivot;
private bool _origPreserveAspect;
private Vector3 _homeScale = Vector3.one;
// Per-drag state
private RectTransform _rt;
@@ -45,6 +46,8 @@ namespace Darkmatter.Features.ShapeBuilder.UI
private Vector2 _trayPosInDragRoot;
private Vector2 _dragSizeDelta;
private Vector3 _dragLocalScale;
private float _dragOrigAlpha;
private Tween _dragScaleTween;
private Sequence _previewSeq;
private Sequence _snapSettle;
private bool _locked;
@@ -68,6 +71,14 @@ namespace Darkmatter.Features.ShapeBuilder.UI
if (image != null) image.color = color;
}
private void SetAlpha(float a)
{
if (image == null) return;
var c = image.color;
c.a = a;
image.color = c;
}
public void Setup(
ShapeSO shape,
SlotMarker[] candidateSlots,
@@ -95,6 +106,7 @@ namespace Darkmatter.Features.ShapeBuilder.UI
_origAnchorMax = RectTransform.anchorMax;
_origPivot = RectTransform.pivot;
_origPreserveAspect = image != null && image.preserveAspect;
_homeScale = RectTransform.localScale;
image.sprite = shape.Sprite;
ApplyTrayPose();
@@ -104,6 +116,10 @@ namespace Darkmatter.Features.ShapeBuilder.UI
{
if (_locked) return;
// Kill any tweens still running on this piece (e.g. a return/preview from a
// re-grab mid-animation) so the new drag starts from a clean, known pose.
Tween.StopAll(onTarget: RectTransform);
if (_dragRoot != null && RectTransform.parent != _dragRoot)
{
RectTransform.SetParent(_dragRoot, worldPositionStays: true);
@@ -114,11 +130,20 @@ namespace Darkmatter.Features.ShapeBuilder.UI
_eventCam = e.pressEventCamera;
_trayPosInDragRoot = RectTransform.anchoredPosition;
_dragSizeDelta = RectTransform.sizeDelta;
_dragLocalScale = RectTransform.localScale;
// Use the canonical resting scale, not the live value, so a re-grab mid-tween
// can't bake a drifted scale into the drag base.
_dragLocalScale = _homeScale;
RectTransform.localScale = _homeScale;
_grabOffset = RectTransform.anchoredPosition - ScreenToLocal(e.position);
_inPreview = false;
Tween.LocalScale(RectTransform, _dragLocalScale * _cfg.DragScale, _cfg.SnapDuration, Ease.OutQuad);
if (image != null)
{
_dragOrigAlpha = image.color.a;
SetAlpha(_cfg.DragAlpha);
}
_dragScaleTween = Tween.Scale(RectTransform, _dragLocalScale * _cfg.DragScale, _cfg.SnapDuration, Ease.OutQuad);
}
public void OnDrag(PointerEventData e)
@@ -156,6 +181,12 @@ namespace Darkmatter.Features.ShapeBuilder.UI
{
if (_locked) return;
// Stop the begin-drag scale-up so it can't fight the return/snap pose.
// Leave _previewSeq alive — the snap path lets it settle.
if (_dragScaleTween.isAlive) _dragScaleTween.Stop();
SetAlpha(_dragOrigAlpha);
var target = FindSlotUnder(e.position);
if (target != null)
{
@@ -184,16 +215,18 @@ namespace Darkmatter.Features.ShapeBuilder.UI
private void AnimatePreviewPose(bool toSlot)
{
if (_dragScaleTween.isAlive) _dragScaleTween.Stop();
if (_previewSeq.isAlive) _previewSeq.Stop();
if (toSlot && _activeSlot != null)
{
var slot = _activeSlot.RectTransform;
SlotPoseInDragSpace(out var slotRot, out var slotScale);
if (image != null) image.preserveAspect = false;
_previewSeq = Sequence.Create()
.Group(Tween.UIAnchoredPosition(RectTransform, SlotPosInDragSpace(), _cfg.SnapDuration, Ease.OutQuad))
.Group(Tween.LocalScale(RectTransform, SlotScaleInDragSpace(), _cfg.SnapDuration, Ease.OutQuad))
.Group(Tween.LocalRotation(RectTransform, SlotRotInDragSpace(), _cfg.SnapDuration, Ease.OutQuad))
.Group(Tween.LocalScale(RectTransform, slotScale, _cfg.SnapDuration, Ease.OutQuad))
.Group(Tween.LocalRotation(RectTransform, slotRot, _cfg.SnapDuration, Ease.OutQuad))
.Group(Tween.UISizeDelta(RectTransform, slot.rect.size, _cfg.SnapDuration, Ease.OutQuad));
}
else
@@ -222,19 +255,25 @@ namespace Darkmatter.Features.ShapeBuilder.UI
_inPreview = false;
}
private Quaternion SlotRotInDragSpace()
// Rotation + signed scale that make the piece (a child of the drag root) match the pose
// it will have once parented into the slot. Derived from the slot's local X/Y axes
// expressed in drag-root space: their angle gives the rotation, their lengths the scale,
// and their handedness the mirror. A quaternion + lossyScale pair can't encode a negative
// (mirrored) scale, which is why flipped slots previewed at the wrong orientation while the
// snapped piece — which inherits the slot's real transform — looked correct.
private void SlotPoseInDragSpace(out Quaternion rot, out Vector3 scale)
{
return Quaternion.Inverse(_parentRect.rotation) * _activeSlot.RectTransform.rotation;
}
var slot = _activeSlot.RectTransform;
Vector3 r = _parentRect.InverseTransformVector(slot.TransformVector(Vector3.right));
Vector3 u = _parentRect.InverseTransformVector(slot.TransformVector(Vector3.up));
private Vector3 SlotScaleInDragSpace()
{
Vector3 parentLossy = _parentRect.lossyScale;
Vector3 slotLossy = _activeSlot.RectTransform.lossyScale;
return new Vector3(
slotLossy.x / Mathf.Max(0.0001f, parentLossy.x),
slotLossy.y / Mathf.Max(0.0001f, parentLossy.y),
slotLossy.z / Mathf.Max(0.0001f, parentLossy.z));
float angle = Mathf.Atan2(r.y, r.x) * Mathf.Rad2Deg;
float sx = new Vector2(r.x, r.y).magnitude;
float sy = new Vector2(u.x, u.y).magnitude;
if (r.x * u.y - r.y * u.x < 0f) sy = -sy; // mirrored hierarchy -> flip one axis
rot = Quaternion.Euler(0f, 0f, angle);
scale = new Vector3(sx, sy, 1f);
}
internal void SnapInternal()
@@ -361,6 +400,7 @@ namespace Darkmatter.Features.ShapeBuilder.UI
{
RectTransform.sizeDelta = _traySize;
RectTransform.localRotation = Quaternion.identity;
RectTransform.localScale = _homeScale;
}
private Vector2 ScreenToLocal(Vector2 screenPos)

View File

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

View File

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

View File

@@ -0,0 +1,31 @@
#if UNITY_EDITOR
using Darkmatter.Features.Tutorial.Systems;
using Darkmatter.Libs.PlayerPrefs;
using UnityEditor;
using UnityEngine;
namespace Darkmatter.Features.Tutorial.Editor
{
/// <summary>
/// QA helpers for the forced-once tutorial (editor only — stripped from player builds).
/// </summary>
public static class TutorialDebugMenu
{
[MenuItem("Tools/Darkmatter/Tutorial/Reset (run again next launch)")]
public static void Reset()
{
ProtectedPlayerPrefs.SetBool(TutorialGateService.CompletedKey, false);
ProtectedPlayerPrefs.Save();
Debug.Log("[Tutorial] Reset. Note: it also requires zero completed drawings to run.");
}
[MenuItem("Tools/Darkmatter/Tutorial/Mark Completed (skip)")]
public static void MarkCompleted()
{
ProtectedPlayerPrefs.SetBool(TutorialGateService.CompletedKey, true);
ProtectedPlayerPrefs.Save();
Debug.Log("[Tutorial] Marked completed — it will not run.");
}
}
}
#endif

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 9a54326b3914a43ffa206b427980908e

View File

@@ -0,0 +1,27 @@
{
"name": "Features.Tutorial",
"rootNamespace": "Darkmatter.Features.Tutorial",
"references": [
"GUID:6a0a834eb41764f12ba55c3fb04a40cb",
"GUID:b4c9f7fbf1e144933a1797dc208ece5f",
"GUID:c1c03c0e5b2f4412b9f2be1c20d6a9b1",
"GUID:564d11c0820a9455c8821cd85e9d0fd1",
"GUID:2ca8c3a66565544118d3d52d3922933b",
"GUID:4cede189a43c349069c614e305683720",
"GUID:eb9b7ee4936ff42bebd83ca110182103",
"GUID:995166e584dda4ff98501f62b07aa9cb",
"GUID:b0214a6008ed146ff8f122a6a9c2f6cc",
"GUID:f51ebe6a0ceec4240a699833d6309b23",
"GUID:80ecb87cae9c44d19824e70ea7229748",
"GUID:6055be8ebefd69e48b49212b09b47b2f"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

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

View File

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

View File

@@ -0,0 +1,34 @@
using Darkmatter.Core.Contracts.Features.Tutorial;
using Darkmatter.Core.Data.Static.Features.Tutorial;
using Darkmatter.Features.Tutorial.Systems;
using Darkmatter.Features.Tutorial.UI;
using Darkmatter.Libs.Installers;
using UnityEngine;
using VContainer;
using VContainer.Unity;
namespace Darkmatter.Features.Tutorial.Installers
{
/// <summary>
/// Registers the tutorial in the Boot scene's RootLifetimeScope so the director, overlay and
/// gate are single instances that survive every Colorbook -> Gameplay scene swap (same pattern
/// as the loading screen). Drop this component on a GameObject in the Boot scene and add it to
/// RootLifetimeScope.serviceModules.
/// </summary>
public class TutorialFeatureModule : MonoBehaviour, IModule
{
[SerializeField] private TutorialOverlayView overlayView;
[SerializeField] private TutorialStepsConfig config;
public void Register(IContainerBuilder builder)
{
if (overlayView != null)
builder.RegisterComponent<ITutorialOverlay>(overlayView);
if (config != null)
builder.RegisterInstance(config);
builder.Register<ITutorialGate, TutorialGateService>(Lifetime.Singleton);
builder.RegisterEntryPoint<TutorialDirector>();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 97ce2c486cc8541d1ab1d83fda7f8eda

View File

@@ -0,0 +1,69 @@
# Tutorial — Unity Editor setup
All C# is in place. These steps are the GUI wiring the code can't do. The tutorial is **forced
once**: a first-time player (no completed drawings, flag unset) is guided through pick → drag →
finish → pick colour → paint → Next, with a watchdog that fails open if anything stalls.
## 1. Create the config asset
- Project window → right-click → **Create ▸ Darkmatter ▸ Tutorial ▸ Steps Config**.
- Save as `Assets/Darkmatter/Data/Settings/Tutorial/TutorialStepsConfig.asset`.
- Edit the step copy / watchdog timeout if desired.
## 2. Overlay prefab — single-image circular cutout
`Assets/Darkmatter/Content/Colorbook UI/Prefabs/UI/TutorialOverlayCanvas.prefab`. Target hierarchy:
```
TutorialOverlayCanvas Canvas (Overlay, Sort Order 5000) + CanvasScaler + GraphicRaycaster
+ CanvasGroup + TutorialFeatureModule (overlayView + config assigned)
Area full-stretch RectTransform (pivot 0.5) + TutorialOverlayView
Dim ONE full-stretch black Image, raycast ON, + TutorialCutoutDim component
Halo ring sprite, raycast OFF
Hand hand sprite, raycast OFF
BubbleRoot
BubbleBg bubble sprite, raycast OFF
Text TMP_Text (Fredoka SemiBold), raycast OFF
```
The dim is a **single full-screen Image**; the `TutorialCutoutDim` component drives the
`Darkmatter/TutorialCutout` shader to punch a soft **circular hole** over the target, and acts as a
raycast filter so taps inside the hole reach the real button while everything else is blocked.
`TutorialCutoutDim` fields: **Cutout Shader** = `Darkmatter/TutorialCutout`, **Dim Image** = the
Dim's own Image. (It builds a material instance at runtime — no material asset needed.)
`TutorialOverlayView` fields: Canvas, RootGroup (root CanvasGroup), **Cutout** (the Dim's
TutorialCutoutDim), Halo, Hand, BubbleRoot, BubbleText.
Sprites (optional polish) under `Assets/Darkmatter/Content/Colorbook UI/Sprites/Tutorial/`:
ring for Halo, finger for Hand (turn **Preserve Aspect** on), bubble for BubbleBg.
## 3. Put it in the Boot scene
Open `Assets/Darkmatter/Scenes/Boot.unity`:
1. Drag the **TutorialOverlayCanvas** prefab into the scene (persistent — Boot is never unloaded,
like the loading screen).
2. Make sure its `TutorialFeatureModule` **Config** field points at `TutorialStepsConfig.asset`.
3. Select the **RootLifetimeScope** GameObject and add the prefab's root (it carries the
`TutorialFeatureModule`) to the **Service Modules** array — same as every other root module.
That's the whole wiring: assign config + drop in Boot + add to serviceModules.
## 4. (Optional) register the prefs key for documentation
The flag is stored via `ProtectedPlayerPrefs` under the key `Tutorial.Completed` (hashed, no
registry validation — it already works). To list it in the editor for clarity, add it via
**Tools ▸ Darkmatter ▸ PlayerPrefs Editor** (type Bool).
## 5. Test
- **Tools ▸ Darkmatter ▸ Tutorial ▸ Reset**, and clear progression (so there are no completed
drawings), then Play from **Boot**.
- After the intro you should be guided through the whole loop. Relaunch → it must NOT reappear.
- See the verification checklist in `/Users/darkmatter/.claude/plans/help-me-design-a-splendid-hearth.md`.
## How it hooks in (no gameplay prefab changes needed)
- The director (root singleton) arms on `IntroCompletedSignal`, then awaits existing gameplay
signals one per step. Spawned targets (catalog cell, piece, colour button, region, Next) are
found at runtime with `FindObjectsByType`, so no scene wiring of targets is required.
- Two tiny signals were added for things Find can't observe: `DrawingCatalogReadySignal`
(catalog presenter) and `ColorSelectedSignal` (coloring state repository).
- Input gating is the overlay CanvasGroup's `blocksRaycasts`: ON for blocking tap steps (dim
swallows touches, the empty hole passes through to the real button), OFF for the drag step so the
piece stays visible and draggable.

View File

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

View File

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

View File

@@ -0,0 +1,374 @@
using System;
using System.Collections.Generic;
using System.Threading;
using Cysharp.Threading.Tasks;
using Darkmatter.Core; // DrawingCatalogReadySignal, OpenArtBook/ColorBook, ReturnToMainMenu
using Darkmatter.Core.Contracts.Features.Tutorial;
using Darkmatter.Core.Data.Signals.Features.AppBoot; // IntroCompletedSignal
using Darkmatter.Core.Data.Signals.Features.Coloring; // ColorApplied/ColorSelected/RegionsInitialized
using Darkmatter.Core.Data.Signals.Features.Drawing; // DrawingSelectedSignal
using Darkmatter.Core.Data.Signals.Features.GameplayFlow; // DrawingCompletedSignal
using Darkmatter.Core.Data.Signals.Features.ShapeBuilder; // ShapeBuilderStarted/PieceSnapped/ShapeAssembled
using Darkmatter.Core.Data.Signals.Features.Tutorial;
using Darkmatter.Core.Data.Static.Features.Tutorial;
using Darkmatter.Features.Coloring.UI; // ColorButton, ColorRegionView
using Darkmatter.Features.DrawingCatalog; // DrawingCatalogButton
using Darkmatter.Features.GameplayFlow.UI; // NextButtonView
using Darkmatter.Features.ShapeBuilder.UI; // ShapePiece, SlotMarker
using Darkmatter.Libs.Observer;
using UnityEngine;
using UnityEngine.UI;
using VContainer.Unity;
namespace Darkmatter.Features.Tutorial.Systems
{
/// <summary>
/// Drives the one-time, forced guided tutorial. A single root-scoped singleton that arms on
/// <see cref="IntroCompletedSignal"/> and walks a fixed, linear sequence by awaiting one gameplay
/// signal per step. Watchdog timeouts make every wait fail open so a child is never trapped.
///
/// Navigation is handled so the overlay never gets stranded: opening the ArtBook or leaving to
/// the menu hides it (and re-shows the current step on return), and going Back to the catalog
/// mid-gameplay restarts the tutorial from step 1. A generation counter keeps a restarted run
/// from racing the cancelled one. Targets are found at runtime via FindObjectsByType.
/// </summary>
public sealed class TutorialDirector : IStartable, IDisposable
{
private readonly IEventBus _bus;
private readonly ITutorialOverlay _overlay;
private readonly ITutorialGate _gate;
private readonly TutorialStepsConfig _config;
private IDisposable _introSub;
private readonly List<IDisposable> _navSubs = new();
private CancellationTokenSource _runCts;
private CancellationToken _ct;
private bool _completed;
private bool _suspended;
private Action _reshow;
private int _stepIndex;
private int _gen;
public TutorialDirector(IEventBus bus, ITutorialOverlay overlay, ITutorialGate gate, TutorialStepsConfig config)
{
_bus = bus;
_overlay = overlay;
_gate = gate;
_config = config;
}
public void Start()
{
_introSub = _bus.Subscribe<IntroCompletedSignal>(OnIntroCompleted);
}
private void OnIntroCompleted(IntroCompletedSignal _)
{
_introSub?.Dispose();
_introSub = null;
if (!_gate.ShouldRun) return;
// Run-lifetime navigation handling (persists across restarts).
_navSubs.Add(_bus.Subscribe<OpenArtBookSignal>(_ => Suspend()));
_navSubs.Add(_bus.Subscribe<ReturnToMainMenuSignal>(_ => Suspend()));
_navSubs.Add(_bus.Subscribe<OpenColorBookSignal>(_ => Resume()));
_navSubs.Add(_bus.Subscribe<DrawingCatalogReadySignal>(OnCatalogReadyGlobal));
StartRun(skipCatalogWait: false);
}
private void StartRun(bool skipCatalogWait)
{
_runCts?.Cancel();
_runCts?.Dispose();
_runCts = new CancellationTokenSource();
_ct = _runCts.Token;
_gen++;
_suspended = false;
_reshow = null;
_stepIndex = 0;
_overlay.HideInstant();
RunAsync(_gen, skipCatalogWait).Forget();
}
// Opening the ArtBook / leaving to menu: hide the overlay but keep awaiting the current step.
private void Suspend()
{
_suspended = true;
_overlay.HideInstant();
}
// Returning to the colour book (ArtBook closed): re-show the current step.
private void Resume()
{
if (!_suspended) return;
_suspended = false;
_reshow?.Invoke();
}
// The catalog re-appeared while we were mid-gameplay -> the player went Back. Restart from
// step 1 (the catalog is already on screen, so skip the wait). Excludes step 6+, whose own
// completion loads the catalog.
private void OnCatalogReadyGlobal(DrawingCatalogReadySignal _)
{
if (!_completed && _stepIndex >= 2 && _stepIndex <= 5)
StartRun(skipCatalogWait: true);
}
private void ShowStep(Action show)
{
_reshow = show;
if (!_suspended) show();
}
private async UniTaskVoid RunAsync(int gen, bool skipCatalogWait)
{
if (gen == _gen) _bus.Publish(new TutorialStartedSignal());
try
{
await StepsAsync(skipCatalogWait);
if (gen == _gen) Complete();
}
catch (OperationCanceledException)
{
// Cancelled by a restart or app shutdown — the newer run (if any) owns the overlay.
}
catch (Exception e)
{
Debug.LogError($"[Tutorial] Aborted with error, failing open: {e}");
if (gen == _gen) Complete();
}
finally
{
if (gen == _gen) _overlay.HideInstant();
}
}
private async UniTask StepsAsync(bool skipCatalogWait)
{
// Step 1 — pick a drawing (Colorbook scene).
_stepIndex = 1;
if (!skipCatalogWait)
{
if (!await WaitForSignalAsync<DrawingCatalogReadySignal>()) return;
}
await UniTask.NextFrame(_ct);
ShowStep(() => ShowBlockingTap(FindFirstCatalogCell(), _config.PickText));
if (!await WaitForActionAsync<DrawingSelectedSignal>()) return;
EndStep("pick", 1);
// Step 2 — drag the first piece (Gameplay scene).
_stepIndex = 2;
if (!await WaitForSignalAsync<ShapeBuilderStartedSignal>()) return;
await UniTask.NextFrame(_ct);
ShowStep(() =>
{
var (pieceRect, slotRect) = FindFirstPieceAndSlot();
if (pieceRect != null) _overlay.ShowDrag(pieceRect, slotRect, _config.DragText);
else Debug.LogWarning("[Tutorial] No draggable piece found for the drag step.");
});
if (!await WaitForActionAsync<PieceSnappedSignal>()) return; // any snap teaches the gesture
EndStep("drag", 2);
// Step 3 — finish the rest of the puzzle freely (non-blocking hint).
_stepIndex = 3;
ShowStep(() => _overlay.ShowTap(null, _config.FinishText, blockInput: false));
if (!await WaitForActionAsync<ShapeAssembledSignal>()) return;
EndStep("finish", 3);
// Step 4 — pick a colour.
_stepIndex = 4;
if (!await WaitForSignalAsync<RegionsInitializedSignal>()) return;
await UniTask.NextFrame(_ct);
ShowStep(() => ShowBlockingTap(FindFirstColorButton(), _config.ColorText));
if (!await WaitForActionAsync<ColorSelectedSignal>()) return;
EndStep("color", 4);
// Step 5 — paint a region.
_stepIndex = 5;
ShowStep(() => ShowBlockingTap(FindLargestRegion(), _config.PaintText));
if (!await WaitForActionAsync<ColorAppliedSignal>()) return;
EndStep("paint", 5);
// Step 6 — press Next.
_stepIndex = 6;
await UniTask.NextFrame(_ct);
ShowStep(() => ShowBlockingTap(FindNextButton(), _config.NextText));
if (!await WaitForActionAsync<DrawingCompletedSignal>()) return;
EndStep("next", 6);
// Step 7 — celebrate.
_stepIndex = 7;
await _overlay.ShowToastAsync(_config.DoneText, _ct);
}
private void ShowBlockingTap(RectTransform target, string message)
{
if (target == null) Debug.LogWarning($"[Tutorial] No target found for step: \"{message}\"");
_overlay.ShowTap(target, message, blockInput: target != null);
}
private void EndStep(string id, int index)
{
_reshow = null;
_bus.Publish(new TutorialStepCompletedSignal(id, index));
_overlay.HideInstant();
}
private void Complete()
{
if (_completed) return;
_completed = true;
_gate.MarkCompleted();
_bus.Publish(new TutorialCompletedSignal());
DisposeNav();
}
// Preconditions (a system/scene must become ready): time out so a stalled load fails open.
private UniTask<bool> WaitForSignalAsync<T>(Func<T, bool> predicate = null) where T : struct =>
WaitCoreAsync(_config.StepTimeoutSeconds, predicate);
// The child's own action (drag/tap/paint): defaults to no timeout (ActionTimeoutSeconds = 0) so
// the hint stays put until they do it; a positive value re-enables a safety net.
private UniTask<bool> WaitForActionAsync<T>(Func<T, bool> predicate = null) where T : struct =>
WaitCoreAsync(_config.ActionTimeoutSeconds, predicate);
private async UniTask<bool> WaitCoreAsync<T>(float timeoutSeconds, Func<T, bool> predicate) where T : struct
{
using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(_ct);
var tcs = new UniTaskCompletionSource<bool>();
var sub = _bus.Subscribe<T>(evt =>
{
if (predicate == null || predicate(evt)) tcs.TrySetResult(true);
});
if (timeoutSeconds > 0f) TimeoutAsync(timeoutSeconds, timeoutCts.Token, tcs).Forget();
try
{
using (_ct.Register(() => tcs.TrySetCanceled(_ct)))
return await tcs.Task;
}
finally
{
sub.Dispose();
timeoutCts.Cancel();
}
}
private async UniTaskVoid TimeoutAsync(float seconds, CancellationToken ct, UniTaskCompletionSource<bool> tcs)
{
try
{
await UniTask.Delay(TimeSpan.FromSeconds(seconds), DelayType.UnscaledDeltaTime, cancellationToken: ct);
}
catch (OperationCanceledException)
{
return;
}
tcs.TrySetResult(false);
}
// ── Runtime target discovery ─────────────────────────────────────────
private static RectTransform FindFirstCatalogCell()
{
var buttons = UnityEngine.Object.FindObjectsByType<DrawingCatalogButton>(FindObjectsSortMode.None);
var cell = LowestSiblingActive(buttons);
ScrollToStart(cell); // catalog may be on another page — bring the first item into view
return cell;
}
private static (RectTransform piece, RectTransform slot) FindFirstPieceAndSlot()
{
var pieces = UnityEngine.Object.FindObjectsByType<ShapePiece>(FindObjectsSortMode.None);
ShapePiece piece = null;
foreach (var p in pieces)
{
if (p == null || p.IsLocked || !p.gameObject.activeInHierarchy) continue;
piece = p;
break;
}
if (piece == null) return (null, null);
RectTransform slotRect = null;
var slots = UnityEngine.Object.FindObjectsByType<SlotMarker>(FindObjectsSortMode.None);
foreach (var s in slots)
{
if (s == null || s.IsOccupied) continue;
if (s.SlotId == piece.PieceId) { slotRect = s.RectTransform; break; }
}
return (piece.RectTransform, slotRect);
}
private static RectTransform FindFirstColorButton()
{
var buttons = UnityEngine.Object.FindObjectsByType<ColorButton>(FindObjectsSortMode.None);
return LowestSiblingActive(buttons);
}
private static RectTransform FindLargestRegion()
{
var regions = UnityEngine.Object.FindObjectsByType<ColorRegionView>(FindObjectsSortMode.None);
ColorRegionView best = null;
float bestArea = -1f;
foreach (var r in regions)
{
if (r == null || !r.gameObject.activeInHierarchy) continue;
var size = ((RectTransform)r.transform).rect.size;
var area = Mathf.Abs(size.x * size.y);
if (area > bestArea) { bestArea = area; best = r; }
}
return best != null ? (RectTransform)best.transform : null;
}
private static RectTransform FindNextButton()
{
var view = UnityEngine.Object.FindFirstObjectByType<NextButtonView>();
return view != null ? (RectTransform)view.transform : null;
}
// The first item = lowest sibling index. Cells are instantiated in data order under a layout
// group, so sibling 0 is the leftmost/first; visibility is handled by scrolling it into view.
private static RectTransform LowestSiblingActive<T>(T[] components) where T : Component
{
T best = null;
var bestIndex = int.MaxValue;
foreach (var c in components)
{
if (c == null || !c.gameObject.activeInHierarchy) continue;
var index = c.transform.GetSiblingIndex();
if (index < bestIndex) { bestIndex = index; best = c; }
}
return best != null ? (RectTransform)best.transform : null;
}
// Scrolls the target's ScrollRect to the start so a paged-off first item becomes visible.
private static void ScrollToStart(RectTransform cell)
{
if (cell == null) return;
var scroll = cell.GetComponentInParent<ScrollRect>();
if (scroll == null) return;
scroll.StopMovement();
if (scroll.horizontal) scroll.horizontalNormalizedPosition = 0f;
if (scroll.vertical) scroll.verticalNormalizedPosition = 1f;
}
private void DisposeNav()
{
foreach (var s in _navSubs) s?.Dispose();
_navSubs.Clear();
}
public void Dispose()
{
_introSub?.Dispose();
DisposeNav();
_runCts?.Cancel();
_runCts?.Dispose();
}
}
}

View File

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

View File

@@ -0,0 +1,33 @@
using Darkmatter.Core.Contracts.Features.Progression;
using Darkmatter.Core.Contracts.Features.Tutorial;
using Darkmatter.Libs.PlayerPrefs;
namespace Darkmatter.Features.Tutorial.Systems
{
public sealed class TutorialGateService : ITutorialGate
{
// Stored through ProtectedPlayerPrefs (keys are hashed, not validated against the registry,
// so a local constant is safe). Optionally register it in Tools > Darkmatter > PlayerPrefs
// Editor for documentation.
public const string CompletedKey = "Tutorial.Completed";
private readonly IProgressionSystem _progression;
public TutorialGateService(IProgressionSystem progression)
{
_progression = progression;
}
// Belt-and-suspenders: a player who cleared prefs but already has finished drawings is not a
// first-timer, so don't re-tutorialize them.
public bool ShouldRun =>
!ProtectedPlayerPrefs.GetBool(CompletedKey, false)
&& _progression.CompletedTemplateIds.Count == 0;
public void MarkCompleted()
{
ProtectedPlayerPrefs.SetBool(CompletedKey, true);
ProtectedPlayerPrefs.Save();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 3ee1f88d72ddf4d99bf5669a36cc52bc

View File

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

View File

@@ -0,0 +1,72 @@
using UnityEngine;
using UnityEngine.UI;
namespace Darkmatter.Features.Tutorial.UI
{
/// <summary>
/// A single full-screen dim Image with a soft circular hole driven by the TutorialCutout shader.
/// Also an <see cref="ICanvasRaycastFilter"/>: when the dim is blocking, taps inside the hole
/// fall through to the real button underneath, taps outside are swallowed.
/// </summary>
[RequireComponent(typeof(Image))]
public sealed class TutorialCutoutDim : MonoBehaviour, ICanvasRaycastFilter
{
[SerializeField] private Shader cutoutShader;
[SerializeField] private Image dimImage;
private Material _material;
private Vector2 _centerScreen;
private float _radiusScreen;
private bool _holeActive;
private static readonly int CenterId = Shader.PropertyToID("_Center");
private static readonly int RadiusId = Shader.PropertyToID("_Radius");
private static readonly int AspectId = Shader.PropertyToID("_Aspect");
private static readonly int SoftnessId = Shader.PropertyToID("_Softness");
private void Awake()
{
if (dimImage == null) dimImage = GetComponent<Image>();
if (cutoutShader != null && dimImage != null)
{
_material = new Material(cutoutShader); // instance — never mutate the asset
dimImage.material = _material;
}
SetVisible(false);
}
/// <summary>Show/hide the dim. Hidden = the Graphic is disabled, so it neither draws nor blocks.</summary>
public void SetVisible(bool visible)
{
if (dimImage != null) dimImage.enabled = visible;
if (!visible) ClearHole();
}
public void SetHole(Vector2 centerScreen, float radiusScreen, float screenWidth, float screenHeight)
{
_centerScreen = centerScreen;
_radiusScreen = radiusScreen;
_holeActive = radiusScreen > 0f && screenHeight > 0f;
if (_material == null || screenHeight <= 0f) return;
_material.SetVector(CenterId, new Vector4(centerScreen.x / screenWidth, centerScreen.y / screenHeight, 0f, 0f));
_material.SetFloat(RadiusId, radiusScreen / screenHeight);
_material.SetFloat(AspectId, screenWidth / screenHeight);
_material.SetFloat(SoftnessId, 0.004f);
}
public void ClearHole()
{
_holeActive = false;
if (_material != null) _material.SetFloat(RadiusId, 0f);
}
// Consulted only when the graphic actually participates in raycasting (parent CanvasGroup
// blocksRaycasts == true). Outside the hole -> block; inside -> pass to whatever's beneath.
public bool IsRaycastLocationValid(Vector2 screenPoint, Camera eventCamera)
{
if (!_holeActive) return true;
return Vector2.Distance(screenPoint, _centerScreen) > _radiusScreen;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 29c836b3d2ed343e6a810f6e7548f487

View File

@@ -0,0 +1,449 @@
using System;
using System.Threading;
using Cysharp.Threading.Tasks;
using Darkmatter.Core.Contracts.Features.Tutorial;
using PrimeTween;
using TMPro;
using UnityEngine;
namespace Darkmatter.Features.Tutorial.UI
{
/// <summary>
/// Persistent guidance overlay. A single full-screen dim with a soft circular hole (see
/// <see cref="TutorialCutoutDim"/>) spotlights the target; an animated hand + halo point at it
/// and an instruction bubble sits above/below. Lives on a high-sortingOrder Canvas in the Boot
/// scene so it renders over — and outlives — every additive gameplay scene.
///
/// Input gating is the whole-overlay CanvasGroup.blocksRaycasts master switch: ON for blocking
/// tap steps; OFF for drag steps, hints and toasts. All targets are tracked live in LateUpdate
/// (catalog cells scroll, pieces are dragged); if a target is destroyed (scene swap) the overlay
/// hides itself. All animation uses unscaled time.
/// </summary>
[RequireComponent(typeof(CanvasGroup))]
public sealed class TutorialOverlayView : MonoBehaviour, ITutorialOverlay
{
private enum Mode { Hidden, Tap, Drag, Centered }
[Header("Canvas")]
[SerializeField] private Canvas canvas;
[SerializeField] private CanvasGroup rootGroup;
[Header("Dim (single image + circular cutout)")]
[SerializeField] private TutorialCutoutDim cutout;
[Header("Pointer")]
[SerializeField] private RectTransform halo;
[SerializeField] private RectTransform hand;
[Header("Bubble")]
[SerializeField] private RectTransform bubbleRoot;
[SerializeField] private TMP_Text bubbleText;
[Header("Tuning")]
[Tooltip("Extra screen pixels added to the spotlight circle radius around the target.")]
[SerializeField] private float holePadding = 44f;
[SerializeField] private float fadeDuration = 0.25f;
[SerializeField] private float toastSeconds = 1.6f;
[SerializeField] private float bubbleGap = 60f;
[SerializeField] private float haloPulseScale = 1.18f;
[SerializeField] private float pulseDuration = 0.6f;
[SerializeField] private float dragHandDuration = 1.1f;
[Header("Drag step")]
[Tooltip("Offset (px) from the hand's pivot to its visible fingertip. The tip is placed on the target so it points accurately; a positive Y drops the hand body below the spot.")]
[SerializeField] private Vector2 dragHandTipOffset = new(0f, 70f);
[Tooltip("Pin the drag bubble to the top (true) or bottom (false) edge so it never covers the play area.")]
[SerializeField] private bool dragBubbleAtTop = true;
[Tooltip("Margin (px) from that edge for the drag bubble.")]
[SerializeField] private float dragBubbleEdgeMargin = 150f;
private RectTransform _area;
private Camera _overlayCam;
private Mode _mode = Mode.Hidden;
private RectTransform _target; // tap target
private RectTransform _dragFrom; // the piece (drag start)
private RectTransform _dragTo; // the slot (drag destination)
private float _dragT;
private Sequence _haloSeq;
private Sequence _handSeq;
private readonly Vector3[] _corners = new Vector3[4];
private bool _ready;
public bool IsReady => _ready && this != null;
private void Awake()
{
CacheRefs();
ApplyHiddenState();
}
private void CacheRefs()
{
if (_ready) return;
_area = (RectTransform)transform;
if (canvas == null) canvas = GetComponentInParent<Canvas>();
if (rootGroup == null) rootGroup = GetComponent<CanvasGroup>();
_overlayCam = canvas != null && canvas.renderMode != RenderMode.ScreenSpaceOverlay
? canvas.worldCamera
: null;
_ready = true;
}
// ── ITutorialOverlay ─────────────────────────────────────────────────
public void ShowTap(RectTransform target, string message, bool blockInput)
{
CacheRefs();
KillAnims();
SetText(message);
_dragFrom = null;
_dragTo = null;
if (target != null)
{
_mode = Mode.Tap;
_target = target;
if (cutout != null) cutout.SetVisible(true);
SetPointerVisible(true);
rootGroup.blocksRaycasts = blockInput;
StartHaloPulse();
StartHandTap();
}
else
{
_mode = Mode.Centered;
_target = null;
if (cutout != null) cutout.SetVisible(false);
SetPointerVisible(false);
rootGroup.blocksRaycasts = false;
}
LayoutNow();
FadeInQuick();
}
public void ShowDrag(RectTransform from, RectTransform to, string message)
{
CacheRefs();
KillAnims();
SetText(message);
// No dim and no blocking — the piece lives under the overlay and must stay visible and
// draggable. Halo + hand travel together along the piece -> slot path, recomputed live.
if (cutout != null) cutout.SetVisible(false);
rootGroup.blocksRaycasts = false;
_target = null;
_dragFrom = from;
_dragTo = to;
_dragT = 0f;
if (from != null)
{
_mode = Mode.Drag;
SetPointerVisible(true);
StartHaloPulse();
}
else
{
_mode = Mode.Centered;
SetPointerVisible(false);
}
LayoutNow();
FadeInQuick();
}
public async UniTask ShowToastAsync(string message, CancellationToken ct)
{
CacheRefs();
KillAnims();
SetText(message);
_mode = Mode.Centered;
_target = null;
_dragFrom = null;
_dragTo = null;
if (cutout != null) cutout.SetVisible(false);
SetPointerVisible(false);
rootGroup.blocksRaycasts = false;
LayoutNow();
await FadeAsync(0f, 1f, fadeDuration, ct);
try
{
await UniTask.Delay(TimeSpan.FromSeconds(toastSeconds), DelayType.UnscaledDeltaTime, cancellationToken: ct);
}
catch (OperationCanceledException)
{
HideInstant();
throw;
}
await FadeAsync(1f, 0f, fadeDuration, CancellationToken.None);
HideInstant();
}
public void HideInstant()
{
CacheRefs();
KillAnims();
ApplyHiddenState();
}
// ── Per-frame layout ─────────────────────────────────────────────────
private void LateUpdate()
{
if (_mode == Mode.Hidden) return;
LayoutNow();
}
private void LayoutNow()
{
switch (_mode)
{
case Mode.Tap:
if (_target == null) { HideInstant(); return; } // destroyed (scene swap) -> self-hide
PlaceSpotlight(_target);
break;
case Mode.Drag:
if (_dragFrom == null) { HideInstant(); return; }
LayoutDrag();
break;
default:
LayoutCentered();
break;
}
}
// Tap: cutout + halo + resting hand + bubble around a static target.
private void PlaceSpotlight(RectTransform target)
{
if (!TryToScreenBounds(target, out var min, out var max)) return;
var centerScreen = (min + max) * 0.5f;
var radiusScreen = (max - min).magnitude * 0.5f + holePadding;
if (cutout != null) cutout.SetHole(centerScreen, radiusScreen, Screen.width, Screen.height);
if (!ScreenToLocal(centerScreen, out var centerLocal)) return;
float scale = Screen.height > 0 ? _area.rect.height / Screen.height : 1f;
float radiusLocal = radiusScreen * scale;
if (halo != null)
{
halo.anchoredPosition = centerLocal;
halo.sizeDelta = Vector2.one * (radiusLocal * 2f);
}
if (hand != null)
hand.anchoredPosition = centerLocal + new Vector2(0f, -radiusLocal * 0.5f);
PositionBubble(centerLocal.y, centerLocal.y + radiusLocal, centerLocal.y - radiusLocal,
_area.rect.height * 0.5f);
}
// Drag: halo + hand glide piece -> slot, recomputed live so it tracks the real positions.
private void LayoutDrag()
{
if (!TryToLocalCenter(_dragFrom, out var a)) return;
Vector2 b = _dragTo != null && TryToLocalCenter(_dragTo, out var bc) ? bc : a;
const float pressT = 0.25f;
const float holdT = 0.5f;
float move = Mathf.Max(0.3f, dragHandDuration);
float period = pressT + move + holdT;
_dragT += Time.unscaledDeltaTime;
if (_dragT >= period) _dragT -= period;
float frac, pressScale;
if (_dragT < pressT) { frac = 0f; pressScale = Mathf.Lerp(1f, 0.8f, _dragT / pressT); }
else if (_dragT < pressT + move) { frac = Mathf.SmoothStep(0f, 1f, (_dragT - pressT) / move); pressScale = 0.8f; }
else { frac = 1f; pressScale = Mathf.Lerp(0.8f, 1f, (_dragT - pressT - move) / holdT); }
Vector2 pos = Vector2.Lerp(a, b, frac);
float scale = Screen.height > 0 ? _area.rect.height / Screen.height : 1f;
float radiusScreen = 120f;
if (TryToScreenBounds(_dragFrom, out var min, out var max))
radiusScreen = (max - min).magnitude * 0.5f + holePadding;
float radiusLocal = radiusScreen * scale;
if (halo != null)
{
halo.anchoredPosition = pos;
halo.sizeDelta = Vector2.one * (radiusLocal * 2f);
}
if (hand != null)
{
// Put the visible fingertip on the target point (halo centre) so it points accurately,
// with the hand body hanging below it.
hand.anchoredPosition = pos - dragHandTipOffset;
hand.localScale = Vector3.one * pressScale;
}
// The drag path spans the play area, so pin the bubble to a screen edge instead of hugging
// the piece — keeps it off the shapes.
PositionBubbleAtEdge(dragBubbleAtTop, dragBubbleEdgeMargin);
}
private void LayoutCentered()
{
if (bubbleRoot == null) return;
float halfH = _area.rect.height * 0.5f;
float bubbleHalf = bubbleRoot.rect.height * 0.5f;
bubbleRoot.anchoredPosition =
new Vector2(0f, Mathf.Clamp(halfH * 0.45f, -halfH + bubbleHalf, halfH - bubbleHalf));
}
private void PositionBubble(float holeCenterY, float holeTopY, float holeBottomY, float halfH)
{
if (bubbleRoot == null) return;
float bubbleHalf = bubbleRoot.rect.height * 0.5f;
float y = holeCenterY <= 0f
? holeTopY + bubbleGap + bubbleHalf
: holeBottomY - bubbleGap - bubbleHalf;
y = Mathf.Clamp(y, -halfH + bubbleHalf, halfH - bubbleHalf);
bubbleRoot.anchoredPosition = new Vector2(0f, y);
}
// Pins the bubble to the top or bottom edge (used for the drag step, whose target spans the
// play area). Centred horizontally so it clears the shapes.
private void PositionBubbleAtEdge(bool top, float margin)
{
if (bubbleRoot == null) return;
float halfH = _area.rect.height * 0.5f;
float bubbleHalf = bubbleRoot.rect.height * 0.5f;
float y = top ? halfH - bubbleHalf - margin : -halfH + bubbleHalf + margin;
y = Mathf.Clamp(y, -halfH + bubbleHalf, halfH - bubbleHalf);
bubbleRoot.anchoredPosition = new Vector2(0f, y);
}
// ── Coordinate conversion (camera-agnostic) ──────────────────────────
private bool TryToScreenBounds(RectTransform target, out Vector2 min, out Vector2 max)
{
min = new Vector2(float.MaxValue, float.MaxValue);
max = new Vector2(float.MinValue, float.MinValue);
if (target == null) return false;
var cam = ResolveCamera(target);
target.GetWorldCorners(_corners);
for (int i = 0; i < 4; i++)
{
var sp = RectTransformUtility.WorldToScreenPoint(cam, _corners[i]);
min = Vector2.Min(min, sp);
max = Vector2.Max(max, sp);
}
return true;
}
private bool TryToLocalCenter(RectTransform target, out Vector2 local)
{
local = default;
if (!TryToScreenBounds(target, out var min, out var max)) return false;
return ScreenToLocal((min + max) * 0.5f, out local);
}
private bool ScreenToLocal(Vector2 screen, out Vector2 local) =>
RectTransformUtility.ScreenPointToLocalPointInRectangle(_area, screen, _overlayCam, out local);
private static Camera ResolveCamera(RectTransform target)
{
var c = target.GetComponentInParent<Canvas>();
if (c == null) return null;
c = c.rootCanvas;
return c.renderMode == RenderMode.ScreenSpaceOverlay ? null : c.worldCamera;
}
// ── Animation ────────────────────────────────────────────────────────
private void StartHaloPulse()
{
if (halo == null) return;
halo.localScale = Vector3.one;
_haloSeq = Sequence.Create(useUnscaledTime: true, cycles: -1)
.Chain(Tween.Scale(halo, Vector3.one * haloPulseScale, pulseDuration, Ease.InOutSine))
.Chain(Tween.Scale(halo, Vector3.one, pulseDuration, Ease.InOutSine));
}
private void StartHandTap()
{
if (hand == null) return;
hand.localScale = Vector3.one;
_handSeq = Sequence.Create(useUnscaledTime: true, cycles: -1)
.Chain(Tween.Scale(hand, Vector3.one * 0.82f, 0.35f, Ease.OutQuad))
.Chain(Tween.Scale(hand, Vector3.one, 0.35f, Ease.OutQuad))
.ChainDelay(0.35f);
}
private void FadeInQuick()
{
if (rootGroup == null) return;
Tween.StopAll(onTarget: rootGroup);
rootGroup.alpha = 0f;
Sequence.Create(useUnscaledTime: true).Chain(Tween.Alpha(rootGroup, 1f, fadeDuration, Ease.OutQuad));
}
private async UniTask FadeAsync(float from, float to, float duration, CancellationToken ct)
{
if (rootGroup == null) return;
Tween.StopAll(onTarget: rootGroup);
rootGroup.alpha = from;
float t = 0f;
while (t < duration)
{
ct.ThrowIfCancellationRequested();
t += Time.unscaledDeltaTime;
rootGroup.alpha = Mathf.Lerp(from, to, duration > 0f ? t / duration : 1f);
await UniTask.Yield(PlayerLoopTiming.Update);
}
rootGroup.alpha = to;
}
// ── State helpers ────────────────────────────────────────────────────
private void ApplyHiddenState()
{
_mode = Mode.Hidden;
_target = null;
_dragFrom = null;
_dragTo = null;
if (cutout != null) cutout.SetVisible(false);
if (rootGroup != null)
{
rootGroup.alpha = 0f;
rootGroup.blocksRaycasts = false;
rootGroup.interactable = false;
}
SetPointerVisible(false);
}
private void SetText(string message)
{
if (bubbleText != null) bubbleText.text = message;
if (bubbleRoot != null) bubbleRoot.gameObject.SetActive(!string.IsNullOrEmpty(message));
}
private void SetPointerVisible(bool visible)
{
if (halo != null) halo.gameObject.SetActive(visible);
if (hand != null) hand.gameObject.SetActive(visible);
}
private void KillAnims()
{
if (_haloSeq.isAlive) _haloSeq.Stop();
if (_handSeq.isAlive) _handSeq.Stop();
_haloSeq = default;
_handSeq = default;
if (halo != null) { Tween.StopAll(onTarget: halo); halo.localScale = Vector3.one; }
if (hand != null) { Tween.StopAll(onTarget: hand); hand.localScale = Vector3.one; }
if (rootGroup != null) Tween.StopAll(onTarget: rootGroup);
}
private void OnDisable()
{
KillAnims();
}
}
}

View File

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

View File

@@ -8,6 +8,7 @@ using Darkmatter.Core.Data.Signals.Features.Drawing;
using Darkmatter.Core.Data.Signals.Features.GameplayFlow;
using Darkmatter.Core.Data.Signals.Features.MainMenu;
using Darkmatter.Core.Data.Signals.Features.ShapeBuilder;
using Darkmatter.Core.Data.Signals.Features.Tutorial;
using Darkmatter.Libs.Observer;
using VContainer.Unity;
@@ -52,6 +53,15 @@ namespace Darkmatter.Services.Analytics
_subs.Add(_bus.Subscribe<GallerySaveStartedSignal>(_ => _analytics.LogEvent("gallery_save_started")));
_subs.Add(_bus.Subscribe<GallerySaveCompletedSignal>(s =>
_analytics.LogEvent("gallery_save_completed", "success", s.Success ? "true" : "false")));
_subs.Add(_bus.Subscribe<TutorialStartedSignal>(_ => _analytics.LogEvent("tutorial_started")));
_subs.Add(_bus.Subscribe<TutorialStepCompletedSignal>(s => _analytics.LogEvent("tutorial_step_completed",
new Dictionary<string, object>
{
["step_id"] = s.StepId,
["step_index"] = s.StepIndex,
})));
_subs.Add(_bus.Subscribe<TutorialCompletedSignal>(_ => _analytics.LogEvent("tutorial_completed")));
}
public void Dispose()

Binary file not shown.

Binary file not shown.

View File

@@ -281,8 +281,8 @@ RectTransform:
m_GameObject: {fileID: 2018849806684805117}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_LocalScale: {x: 1.4066, y: 1.4066, z: 1.4066}
m_ConstrainProportionsScale: 1
m_Children:
- {fileID: 86201234489763977}
- {fileID: 109331856778429183}

View File

@@ -26,8 +26,8 @@ RectTransform:
m_GameObject: {fileID: 970882496690282873}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_LocalScale: {x: 1.1503, y: 1.1503, z: 1.1503}
m_ConstrainProportionsScale: 1
m_Children:
- {fileID: 4862617518150298184}
- {fileID: 1927978426257304632}

View File

@@ -101,8 +101,8 @@ RectTransform:
m_GameObject: {fileID: 6408485857370138169}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_LocalScale: {x: 1.2033, y: 1.2033, z: 1.2033}
m_ConstrainProportionsScale: 1
m_Children:
- {fileID: 1214920657407418962}
- {fileID: 6162131388587199005}

View File

@@ -101,8 +101,8 @@ RectTransform:
m_GameObject: {fileID: 830151417515138702}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_LocalScale: {x: 1.2121, y: 1.2121, z: 1.2121}
m_ConstrainProportionsScale: 1
m_Children:
- {fileID: 1060224653914720711}
- {fileID: 3409973025274235484}

View File

@@ -930,7 +930,7 @@ GameObject:
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
m_IsActive: 0
--- !u!224 &3765577967584406493
RectTransform:
m_ObjectHideFlags: 0
@@ -2013,8 +2013,8 @@ RectTransform:
m_GameObject: {fileID: 6684381930794325998}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_LocalScale: {x: 1.2563, y: 1.2563, z: 1.2563}
m_ConstrainProportionsScale: 1
m_Children:
- {fileID: 4001251692510412888}
- {fileID: 9065922355177954181}
@@ -2022,7 +2022,7 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: -324.28992}
m_AnchoredPosition: {x: 18, y: -324.28992}
m_SizeDelta: {x: 484.2721, y: 79.242615}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!1 &6720628983119242459
@@ -3192,8 +3192,8 @@ RectTransform:
m_GameObject: {fileID: 8914662876087302500}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_LocalScale: {x: 1.2563, y: 1.2563, z: 1.2563}
m_ConstrainProportionsScale: 1
m_Children:
- {fileID: 4452683220369286777}
- {fileID: 1884834246100035824}
@@ -3201,7 +3201,7 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0}
m_AnchorMax: {x: 0.5, y: 0}
m_AnchoredPosition: {x: -0.000030517578, y: 39.621277}
m_AnchoredPosition: {x: -33, y: 39.621277}
m_SizeDelta: {x: 484.27228, y: 79.242615}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!1 &9047034408538749912

View File

@@ -28,8 +28,8 @@ RectTransform:
m_GameObject: {fileID: 8269026611177622940}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_LocalScale: {x: 1.1414, y: 1.1414, z: 1.1414}
m_ConstrainProportionsScale: 1
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}

View File

@@ -0,0 +1,761 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &100100
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 100101}
- component: {fileID: 100102}
- component: {fileID: 100103}
- component: {fileID: 100104}
- component: {fileID: 100105}
- component: {fileID: 100107}
m_Layer: 5
m_Name: TutorialOverlayCanvas
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &100101
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 100100}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 0, y: 0, z: 0}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 100111}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0, y: 0}
--- !u!223 &100102
Canvas:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 100100}
m_Enabled: 1
serializedVersion: 3
m_RenderMode: 0
m_Camera: {fileID: 0}
m_PlaneDistance: 100
m_PixelPerfect: 0
m_ReceivesEvents: 1
m_OverrideSorting: 0
m_OverridePixelPerfect: 0
m_SortingBucketNormalizedSize: 0
m_VertexColorAlwaysGammaSpace: 1
m_UseReflectionProbes: 0
m_AdditionalShaderChannelsFlag: 25
m_UpdateRectTransformForStandalone: 0
m_SortingLayerID: 0
m_SortingOrder: 5000
m_TargetDisplay: 0
--- !u!114 &100103
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 100100}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3}
m_Name:
m_EditorClassIdentifier:
m_UiScaleMode: 1
m_ReferencePixelsPerUnit: 100
m_ScaleFactor: 1
m_ReferenceResolution: {x: 1080, y: 1920}
m_ScreenMatchMode: 0
m_MatchWidthOrHeight: 0.5
m_PhysicalUnit: 3
m_FallbackScreenDPI: 96
m_DefaultSpriteDPI: 96
m_DynamicPixelsPerUnit: 1
m_PresetInfoIsWorld: 0
--- !u!114 &100104
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 100100}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3}
m_Name:
m_EditorClassIdentifier:
m_IgnoreReversedGraphics: 1
m_BlockingObjects: 0
m_BlockingMask:
serializedVersion: 2
m_Bits: 4294967295
--- !u!225 &100105
CanvasGroup:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 100100}
m_Enabled: 1
m_Alpha: 0
m_Interactable: 0
m_BlocksRaycasts: 0
m_IgnoreParentGroups: 0
--- !u!114 &100107
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 100100}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 97ce2c486cc8541d1ab1d83fda7f8eda, type: 3}
m_Name:
m_EditorClassIdentifier:
overlayView: {fileID: 100106}
config: {fileID: 11400000, guid: 71357eb1222bb4151ab4e5697a1decd3, type: 2}
--- !u!1 &100110
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 100111}
- component: {fileID: 100106}
- component: {fileID: 6008915593776814208}
m_Layer: 5
m_Name: Area
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &100111
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 100110}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 100201}
- {fileID: 100601}
- {fileID: 100701}
- {fileID: 100801}
m_Father: {fileID: 100101}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &100106
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 100110}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 4ecf127118699405ebcd0e0712d7373d, type: 3}
m_Name:
m_EditorClassIdentifier:
canvas: {fileID: 100102}
rootGroup: {fileID: 100105}
cutout: {fileID: 100204}
halo: {fileID: 100601}
hand: {fileID: 100701}
bubbleRoot: {fileID: 100801}
bubbleText: {fileID: 101003}
holePadding: 44
fadeDuration: 0.25
toastSeconds: 1.6
bubbleGap: 60
haloPulseScale: 1.18
pulseDuration: 0.6
dragHandDuration: 1.1
--- !u!225 &6008915593776814208
CanvasGroup:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 100110}
m_Enabled: 1
m_Alpha: 1
m_Interactable: 1
m_BlocksRaycasts: 1
m_IgnoreParentGroups: 0
--- !u!1 &100200
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 100201}
- component: {fileID: 100202}
- component: {fileID: 100203}
- component: {fileID: 100204}
m_Layer: 5
m_Name: Dim
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &100201
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 100200}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 100111}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &100202
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 100200}
m_CullTransparentMesh: 1
--- !u!114 &100203
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 100200}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 0, g: 0, b: 0, a: 0.72}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 0}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!114 &100204
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 100200}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 29c836b3d2ed343e6a810f6e7548f487, type: 3}
m_Name:
m_EditorClassIdentifier:
cutoutShader: {fileID: 4800000, guid: 57d1ed1c62afa45db97a7c8e9ace795c, type: 3}
dimImage: {fileID: 100203}
--- !u!1 &100600
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 100601}
- component: {fileID: 100602}
- component: {fileID: 100603}
m_Layer: 5
m_Name: Halo
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &100601
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 100600}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 100111}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 240, y: 240}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &100602
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 100600}
m_CullTransparentMesh: 1
--- !u!114 &100603
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 100600}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 0.9}
m_RaycastTarget: 0
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 21300000, guid: 17078a4ce7e7d450e85816637b6b6bbe, type: 3}
m_Type: 0
m_PreserveAspect: 1
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!1 &100700
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 100701}
- component: {fileID: 100702}
- component: {fileID: 100703}
m_Layer: 5
m_Name: Hand
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &100701
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 100700}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 100111}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 130, y: 130}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &100702
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 100700}
m_CullTransparentMesh: 1
--- !u!114 &100703
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 100700}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 0
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 5357005990035746724, guid: edf2d0d62497c37488be3b7304708f09, type: 3}
m_Type: 0
m_PreserveAspect: 1
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!1 &100800
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 100801}
m_Layer: 5
m_Name: BubbleRoot
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &100801
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 100800}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 100901}
m_Father: {fileID: 100111}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 350}
m_SizeDelta: {x: 760, y: 180}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!1 &100900
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 100901}
- component: {fileID: 100902}
- component: {fileID: 100903}
- component: {fileID: 6057957808347567821}
- component: {fileID: 3730188213376167672}
m_Layer: 5
m_Name: BubbleBg
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &100901
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 100900}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 101001}
m_Father: {fileID: 100801}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 91}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &100902
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 100900}
m_CullTransparentMesh: 1
--- !u!114 &100903
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 100900}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 0.96}
m_RaycastTarget: 0
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 21300000, guid: 392a0995832130344b5e8918edc0052f, type: 3}
m_Type: 1
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!114 &6057957808347567821
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 100900}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 3245ec927659c4140ac4f8d17403cc18, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.ContentSizeFitter
m_HorizontalFit: 2
m_VerticalFit: 0
--- !u!114 &3730188213376167672
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 100900}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 59f8146938fff824cb5fd77236b75775, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.VerticalLayoutGroup
m_Padding:
m_Left: 100
m_Right: 100
m_Top: -80
m_Bottom: 0
m_ChildAlignment: 3
m_Spacing: 0
m_ChildForceExpandWidth: 0
m_ChildForceExpandHeight: 0
m_ChildControlWidth: 0
m_ChildControlHeight: 0
m_ChildScaleWidth: 1
m_ChildScaleHeight: 1
m_ReverseArrangement: 0
--- !u!1 &101000
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 101001}
- component: {fileID: 101002}
- component: {fileID: 101003}
- component: {fileID: 8870904446750593947}
m_Layer: 5
m_Name: Text
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &101001
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 101000}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 100901}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 463.445, y: -95.50001}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &101002
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 101000}
m_CullTransparentMesh: 1
--- !u!114 &101003
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 101000}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.TextMeshPro::TMPro.TextMeshProUGUI
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 0
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_text: Tutorial
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: dde468a43b0440f4a9d121fb1d8f290e, type: 2}
m_sharedMaterial: {fileID: -1548830327015913602, guid: dde468a43b0440f4a9d121fb1d8f290e, type: 2}
m_fontSharedMaterials: []
m_fontMaterial: {fileID: 0}
m_fontMaterials: []
m_fontColor32:
serializedVersion: 2
rgba: 4281542953
m_fontColor: {r: 0.16, g: 0.16, b: 0.2, a: 1}
m_enableVertexGradient: 0
m_colorMode: 3
m_fontColorGradient:
topLeft: {r: 1, g: 1, b: 1, a: 1}
topRight: {r: 1, g: 1, b: 1, a: 1}
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
bottomRight: {r: 1, g: 1, b: 1, a: 1}
m_fontColorGradientPreset: {fileID: 0}
m_spriteAsset: {fileID: 0}
m_tintAllSprites: 0
m_StyleSheet: {fileID: 0}
m_TextStyleHashCode: -1183493901
m_overrideHtmlColors: 0
m_faceColor:
serializedVersion: 2
rgba: 4294967295
m_fontSize: 60
m_fontSizeBase: 46
m_fontWeight: 400
m_enableAutoSizing: 1
m_fontSizeMin: 24
m_fontSizeMax: 60
m_fontStyle: 0
m_HorizontalAlignment: 2
m_VerticalAlignment: 512
m_textAlignment: 65535
m_characterSpacing: 0
m_characterHorizontalScale: 1
m_wordSpacing: 0
m_lineSpacing: 0
m_lineSpacingMax: 0
m_paragraphSpacing: 0
m_charWidthMaxAdj: 0
m_TextWrappingMode: 0
m_wordWrappingRatios: 0.4
m_overflowMode: 0
m_linkedTextComponent: {fileID: 0}
parentLinkedComponent: {fileID: 0}
m_enableKerning: 0
m_ActiveFontFeatures: 6e72656b
m_enableExtraPadding: 0
checkPaddingRequired: 0
m_isRichText: 1
m_EmojiFallbackSupport: 1
m_parseCtrlCharacters: 1
m_isOrthographic: 1
m_isCullingEnabled: 0
m_horizontalMapping: 0
m_verticalMapping: 0
m_uvLineOffset: 0
m_geometrySortingOrder: 0
m_IsTextObjectScaleStatic: 0
m_VertexBufferAutoSizeReduction: 0
m_useMaxVisibleDescender: 1
m_pageToDisplay: 1
m_margin: {x: 0, y: 0, z: 0, w: 0}
m_isUsingLegacyAnimationComponent: 0
m_isVolumetricText: 0
m_hasFontAssetChanged: 0
m_baseMaterial: {fileID: 0}
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
--- !u!114 &8870904446750593947
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 101000}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 3e37e2b5f1962004c8f10779a2fcf23a, type: 3}
m_Name:
m_EditorClassIdentifier: uLayout::Poke.UI.LayoutText
m_log: 0
m_ignoreLayout: 0
m_sizing:
x: 0
y: 0

View File

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

View File

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

View File

@@ -0,0 +1,102 @@
Shader "Darkmatter/TutorialCutout"
{
// Full-screen UI dim with a soft circular hole punched out. The hole centre/radius are driven
// from script in UV space (0..1 across the image, which covers the screen). Radius 0 = solid dim.
Properties
{
[PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
_Color ("Tint", Color) = (1,1,1,1)
_Center ("Hole Center (UV)", Vector) = (0.5, 0.5, 0, 0)
_Radius ("Hole Radius", Float) = 0
_Softness ("Edge Softness", Float) = 0.004
_Aspect ("Aspect (w/h)", Float) = 1
_StencilComp ("Stencil Comparison", Float) = 8
_Stencil ("Stencil ID", Float) = 0
_StencilOp ("Stencil Operation", Float) = 0
_StencilWriteMask ("Stencil Write Mask", Float) = 255
_StencilReadMask ("Stencil Read Mask", Float) = 255
_ColorMask ("Color Mask", Float) = 15
}
SubShader
{
Tags
{
"Queue"="Transparent"
"IgnoreProjector"="True"
"RenderType"="Transparent"
"PreviewType"="Plane"
"CanUseSpriteAtlas"="True"
}
Stencil
{
Ref [_Stencil]
Comp [_StencilComp]
Pass [_StencilOp]
ReadMask [_StencilReadMask]
WriteMask [_StencilWriteMask]
}
Cull Off
Lighting Off
ZWrite Off
ZTest [unity_GUIZTestMode]
Blend SrcAlpha OneMinusSrcAlpha
ColorMask [_ColorMask]
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata_t
{
float4 vertex : POSITION;
float4 color : COLOR;
float2 texcoord : TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
};
sampler2D _MainTex;
fixed4 _Color;
float4 _Center;
float _Radius;
float _Softness;
float _Aspect;
v2f vert(appdata_t v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.texcoord = v.texcoord;
o.color = v.color * _Color;
return o;
}
fixed4 frag(v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.texcoord) * i.color;
float2 d = i.texcoord - _Center.xy;
d.x *= _Aspect; // keep the hole round on non-square screens
float dist = length(d);
// 0 inside the hole -> fully transparent, 1 outside -> full dim.
float cut = smoothstep(_Radius - _Softness, _Radius + _Softness, dist);
col.a *= cut;
return col;
}
ENDCG
}
}
}

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 57d1ed1c62afa45db97a7c8e9ace795c
ShaderImporter:
externalObjects: {}
defaultTextures: []
nonModifiableTextures: []
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -49,7 +49,7 @@ TextureImporter:
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteBorder: {x: 270, y: 191, z: 255, w: 110}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
@@ -119,6 +119,19 @@ TextureImporter:
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: iOS
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: []
@@ -127,7 +140,7 @@ TextureImporter:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
internalID: 1537655665
vertices: []
indices:
edges: []

View File

@@ -14,6 +14,6 @@ MonoBehaviour:
m_EditorClassIdentifier: Core::Darkmatter.Core.Data.Static.Services.Ads.AdUnitCatalogSO
androidAppId: ca-app-pub-3940256099942544~3347511713
iosAppId: ca-app-pub-3940256099942544~1458002511
useTestUnits: 1
useTestUnits: 0
testDeviceIds: []
entries: []

View File

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

View File

@@ -0,0 +1,22 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5c513430cc85f4357b3df1da019bf554, type: 3}
m_Name: TutorialStepsConfig
m_EditorClassIdentifier: Core::Darkmatter.Core.Data.Static.Features.Tutorial.TutorialStepsConfig
pickText: Pick a picture to start!
dragText: Drag the piece to its spot!
finishText: Now finish the puzzle!
colorText: Choose a color!
paintText: Tap the picture to color it!
nextText: Tap Next when you're done!
doneText: Yay! You did it!
stepTimeoutSeconds: 45

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 71357eb1222bb4151ab4e5697a1decd3
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1526,8 +1526,55 @@ Transform:
- {fileID: 1043308346}
- {fileID: 610419919}
- {fileID: 789049882}
- {fileID: 777749261}
m_Father: {fileID: 1798580248}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &777749260
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 777749261}
- component: {fileID: 777749262}
m_Layer: 0
m_Name: Tutorial Feature Module
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &777749261
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 777749260}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 752713007}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &777749262
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 777749260}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 97ce2c486cc8541d1ab1d83fda7f8eda, type: 3}
m_Name:
m_EditorClassIdentifier: Features.Tutorial::Darkmatter.Features.Tutorial.Installers.TutorialFeatureModule
overlayView: {fileID: 1976259501}
config: {fileID: 11400000, guid: 71357eb1222bb4151ab4e5697a1decd3, type: 2}
--- !u!1 &789049881
GameObject:
m_ObjectHideFlags: 0
@@ -2319,6 +2366,7 @@ MonoBehaviour:
- {fileID: 361052052}
- {fileID: 1707278034}
- {fileID: 164240471}
- {fileID: 777749262}
--- !u!1 &1890425864
GameObject:
m_ObjectHideFlags: 0
@@ -2365,6 +2413,158 @@ MonoBehaviour:
m_EditorClassIdentifier: Features.AppBoot::Darkmatter.Features.AppBoot.SceneRefs.AppBootSceneRefs
<IntroVideoPlayer>k__BackingField: {fileID: 2122267604}
<IntroCanvas>k__BackingField: {fileID: 2133561494}
--- !u!1001 &1976259500
PrefabInstance:
m_ObjectHideFlags: 0
serializedVersion: 2
m_Modification:
serializedVersion: 3
m_TransformParent: {fileID: 0}
m_Modifications:
- target: {fileID: 100100, guid: 7d10b17c0a1e4e7d9b2f8c6a5d4e3f22, type: 3}
propertyPath: m_Name
value: TutorialOverlayCanvas
objectReference: {fileID: 0}
- target: {fileID: 100101, guid: 7d10b17c0a1e4e7d9b2f8c6a5d4e3f22, type: 3}
propertyPath: m_Pivot.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 100101, guid: 7d10b17c0a1e4e7d9b2f8c6a5d4e3f22, type: 3}
propertyPath: m_Pivot.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 100101, guid: 7d10b17c0a1e4e7d9b2f8c6a5d4e3f22, type: 3}
propertyPath: m_AnchorMax.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 100101, guid: 7d10b17c0a1e4e7d9b2f8c6a5d4e3f22, type: 3}
propertyPath: m_AnchorMax.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 100101, guid: 7d10b17c0a1e4e7d9b2f8c6a5d4e3f22, type: 3}
propertyPath: m_AnchorMin.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 100101, guid: 7d10b17c0a1e4e7d9b2f8c6a5d4e3f22, type: 3}
propertyPath: m_AnchorMin.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 100101, guid: 7d10b17c0a1e4e7d9b2f8c6a5d4e3f22, type: 3}
propertyPath: m_SizeDelta.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 100101, guid: 7d10b17c0a1e4e7d9b2f8c6a5d4e3f22, type: 3}
propertyPath: m_SizeDelta.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 100101, guid: 7d10b17c0a1e4e7d9b2f8c6a5d4e3f22, type: 3}
propertyPath: m_LocalPosition.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 100101, guid: 7d10b17c0a1e4e7d9b2f8c6a5d4e3f22, type: 3}
propertyPath: m_LocalPosition.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 100101, guid: 7d10b17c0a1e4e7d9b2f8c6a5d4e3f22, type: 3}
propertyPath: m_LocalPosition.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 100101, guid: 7d10b17c0a1e4e7d9b2f8c6a5d4e3f22, type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 100101, guid: 7d10b17c0a1e4e7d9b2f8c6a5d4e3f22, type: 3}
propertyPath: m_LocalRotation.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 100101, guid: 7d10b17c0a1e4e7d9b2f8c6a5d4e3f22, type: 3}
propertyPath: m_LocalRotation.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 100101, guid: 7d10b17c0a1e4e7d9b2f8c6a5d4e3f22, type: 3}
propertyPath: m_LocalRotation.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 100101, guid: 7d10b17c0a1e4e7d9b2f8c6a5d4e3f22, type: 3}
propertyPath: m_AnchoredPosition.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 100101, guid: 7d10b17c0a1e4e7d9b2f8c6a5d4e3f22, type: 3}
propertyPath: m_AnchoredPosition.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 100101, guid: 7d10b17c0a1e4e7d9b2f8c6a5d4e3f22, type: 3}
propertyPath: m_LocalEulerAnglesHint.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 100101, guid: 7d10b17c0a1e4e7d9b2f8c6a5d4e3f22, type: 3}
propertyPath: m_LocalEulerAnglesHint.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 100101, guid: 7d10b17c0a1e4e7d9b2f8c6a5d4e3f22, type: 3}
propertyPath: m_LocalEulerAnglesHint.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 100603, guid: 7d10b17c0a1e4e7d9b2f8c6a5d4e3f22, type: 3}
propertyPath: m_Enabled
value: 0
objectReference: {fileID: 0}
- target: {fileID: 100701, guid: 7d10b17c0a1e4e7d9b2f8c6a5d4e3f22, type: 3}
propertyPath: m_SizeDelta.x
value: 200
objectReference: {fileID: 0}
- target: {fileID: 100701, guid: 7d10b17c0a1e4e7d9b2f8c6a5d4e3f22, type: 3}
propertyPath: m_SizeDelta.y
value: 200
objectReference: {fileID: 0}
- target: {fileID: 100901, guid: 7d10b17c0a1e4e7d9b2f8c6a5d4e3f22, type: 3}
propertyPath: m_SizeDelta.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 101001, guid: 7d10b17c0a1e4e7d9b2f8c6a5d4e3f22, type: 3}
propertyPath: m_AnchorMax.y
value: 1
objectReference: {fileID: 0}
- target: {fileID: 101001, guid: 7d10b17c0a1e4e7d9b2f8c6a5d4e3f22, type: 3}
propertyPath: m_AnchorMin.y
value: 1
objectReference: {fileID: 0}
- target: {fileID: 101001, guid: 7d10b17c0a1e4e7d9b2f8c6a5d4e3f22, type: 3}
propertyPath: m_SizeDelta.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 101001, guid: 7d10b17c0a1e4e7d9b2f8c6a5d4e3f22, type: 3}
propertyPath: m_SizeDelta.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 101001, guid: 7d10b17c0a1e4e7d9b2f8c6a5d4e3f22, type: 3}
propertyPath: m_AnchoredPosition.x
value: 207.45999
objectReference: {fileID: 0}
- target: {fileID: 101001, guid: 7d10b17c0a1e4e7d9b2f8c6a5d4e3f22, type: 3}
propertyPath: m_AnchoredPosition.y
value: -95.50001
objectReference: {fileID: 0}
- target: {fileID: 101003, guid: 7d10b17c0a1e4e7d9b2f8c6a5d4e3f22, type: 3}
propertyPath: m_fontSize
value: 60
objectReference: {fileID: 0}
m_RemovedComponents: []
m_RemovedGameObjects: []
m_AddedGameObjects: []
m_AddedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: 7d10b17c0a1e4e7d9b2f8c6a5d4e3f22, type: 3}
--- !u!114 &1976259501 stripped
MonoBehaviour:
m_CorrespondingSourceObject: {fileID: 100106, guid: 7d10b17c0a1e4e7d9b2f8c6a5d4e3f22, type: 3}
m_PrefabInstance: {fileID: 1976259500}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 4ecf127118699405ebcd0e0712d7373d, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!1 &2122267603
GameObject:
m_ObjectHideFlags: 0
@@ -2553,3 +2753,4 @@ SceneRoots:
- {fileID: 673724413}
- {fileID: 1156238481}
- {fileID: 680382929}
- {fileID: 1976259500}

View File

@@ -164,7 +164,6 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier: Features.Capture::Darkmatter.Features.Capture.CaptureFeatureModule
captureScale: 1
galleryBackground: {fileID: 2800000, guid: 0c8e208e83531f84cb2b842025cdd232, type: 3}
captureButtonView: {fileID: 376589371}
gallerySaveView: {fileID: 0}
--- !u!1 &64614225
@@ -551,7 +550,7 @@ RectTransform:
m_GameObject: {fileID: 259035377}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1.4399601, y: 1.4399601, z: 1.4399601}
m_LocalScale: {x: 1.8345093, y: 1.8345093, z: 1.8345093}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 1176170784}
@@ -559,7 +558,7 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 1, y: 0}
m_AnchorMax: {x: 1, y: 0}
m_AnchoredPosition: {x: -622.2647, y: 212}
m_AnchoredPosition: {x: -718, y: 244}
m_SizeDelta: {x: 407.377, y: 163}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &259035379
@@ -821,7 +820,7 @@ RectTransform:
m_GameObject: {fileID: 376589366}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1.2278218, y: 1.2278218, z: 1.2278218}
m_LocalScale: {x: 1.3688985, y: 1.3688985, z: 1.3688985}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 153461769}
@@ -2437,7 +2436,7 @@ RectTransform:
m_GameObject: {fileID: 1310839948}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_LocalScale: {x: 1.1945, y: 1.1945, z: 1.1945}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 2058063738}
@@ -2446,7 +2445,7 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 505.82385, y: 200.40308}
m_AnchoredPosition: {x: 505.82385, y: 257}
m_SizeDelta: {x: 660.4729, y: 319.02832}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &1310839950
@@ -3360,10 +3359,6 @@ PrefabInstance:
propertyPath: m_Name
value: ArtBookView
objectReference: {fileID: 0}
- target: {fileID: 3521470624751981147, guid: 4dd2a1cb2754a410f9f807f563a55e7c, type: 3}
propertyPath: m_IsActive
value: 0
objectReference: {fileID: 0}
- target: {fileID: 3765577967584406493, guid: 4dd2a1cb2754a410f9f807f563a55e7c, type: 3}
propertyPath: m_Pivot.x
value: 0.5

View File

@@ -1,13 +1,9 @@
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 \
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<?xml version="1.0" encoding="utf-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 \&#xD;&#xA;http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.google.firebase</groupId>
<artifactId>firebase-analytics-unity</artifactId>
<version>13.11.0</version>
<packaging>aar</packaging>
<dependencies>
</dependencies>
<packaging>srcaar</packaging>
<dependencies></dependencies>
</project>

View File

@@ -1,13 +1,9 @@
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 \
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<?xml version="1.0" encoding="utf-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 \&#xD;&#xA;http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.google.firebase</groupId>
<artifactId>firebase-app-unity</artifactId>
<version>13.11.0</version>
<packaging>aar</packaging>
<dependencies>
</dependencies>
<packaging>srcaar</packaging>
<dependencies></dependencies>
</project>

View File

@@ -1,13 +1,9 @@
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 \
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<?xml version="1.0" encoding="utf-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 \&#xD;&#xA;http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.google.firebase</groupId>
<artifactId>firebase-crashlytics-unity</artifactId>
<version>13.11.0</version>
<packaging>aar</packaging>
<dependencies>
</dependencies>
<packaging>srcaar</packaging>
<dependencies></dependencies>
</project>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,28 @@
fileFormatVersion: 2
guid: 29951eef4f9054aa88743cf6c4efbaae
labels:
- gpsr
PluginImporter:
externalObjects: {}
serializedVersion: 3
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
Android:
enabled: 0
settings: {}
Any:
enabled: 0
settings: {}
Editor:
enabled: 0
settings:
DefaultValueInitialized: true
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 \&#xD;&#xA;http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.google.firebase</groupId>
<artifactId>firebase-analytics-unity</artifactId>
<version>13.11.0</version>
<packaging>aar</packaging>
<dependencies></dependencies>
</project>

View File

@@ -1,5 +1,7 @@
fileFormatVersion: 2
guid: 95b36b0e05811724fa9ff82f0e1b72ed
guid: 07873cfa577e34134a09d460a78b614c
labels:
- gpsr
DefaultImporter:
externalObjects: {}
userData:

View File

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

View File

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

View File

@@ -0,0 +1,28 @@
fileFormatVersion: 2
guid: f6c1d3b42f439488c86ed09c55566281
labels:
- gpsr
PluginImporter:
externalObjects: {}
serializedVersion: 3
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
Android:
enabled: 0
settings: {}
Any:
enabled: 0
settings: {}
Editor:
enabled: 0
settings:
DefaultValueInitialized: true
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 \&#xD;&#xA;http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.google.firebase</groupId>
<artifactId>firebase-app-unity</artifactId>
<version>13.11.0</version>
<packaging>aar</packaging>
<dependencies></dependencies>
</project>

View File

@@ -1,5 +1,7 @@
fileFormatVersion: 2
guid: 3509e33d6780510448b1f27a6ad6b84f
guid: bcd4da93c567b4aa19035656e33aaf4b
labels:
- gpsr
DefaultImporter:
externalObjects: {}
userData:

View File

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

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