Compare commits
16 Commits
c6b06bb5ee
...
d700760524
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d700760524 | ||
|
|
ac0f3a458a | ||
|
|
113b82bd46 | ||
|
|
3e5ff544bf | ||
|
|
b74af8867e | ||
|
|
b669bcc0e0 | ||
|
|
9b6100b5bb | ||
|
|
f51286468a | ||
|
|
8a4a84dadf | ||
|
|
9b2e4c915c | ||
|
|
84689a6755 | ||
|
|
41c9969996 | ||
|
|
0b22ed6d09 | ||
|
|
47fb204446 | ||
|
|
676b389244 | ||
|
|
931515193a |
@@ -15,7 +15,7 @@ MonoBehaviour:
|
|||||||
m_DefaultGroup: 0e030d5498bfe4ffd8443c796618c539
|
m_DefaultGroup: 0e030d5498bfe4ffd8443c796618c539
|
||||||
m_currentHash:
|
m_currentHash:
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
Hash: fdf5dbef4b3bdd1999753be21e456785
|
Hash: 00000000000000000000000000000000
|
||||||
m_OptimizeCatalogSize: 0
|
m_OptimizeCatalogSize: 0
|
||||||
m_BuildRemoteCatalog: 0
|
m_BuildRemoteCatalog: 0
|
||||||
m_CatalogRequestsTimeout: 0
|
m_CatalogRequestsTimeout: 0
|
||||||
@@ -58,7 +58,7 @@ MonoBehaviour:
|
|||||||
m_ContentStateBuildPathProfileVariableName:
|
m_ContentStateBuildPathProfileVariableName:
|
||||||
m_CustomContentStateBuildPath:
|
m_CustomContentStateBuildPath:
|
||||||
m_ContentStateBuildPath:
|
m_ContentStateBuildPath:
|
||||||
m_BuildAddressablesWithPlayerBuild: 0
|
m_BuildAddressablesWithPlayerBuild: 1
|
||||||
m_overridePlayerVersion: '[UnityEditor.PlayerSettings.bundleVersion]'
|
m_overridePlayerVersion: '[UnityEditor.PlayerSettings.bundleVersion]'
|
||||||
m_GroupAssets:
|
m_GroupAssets:
|
||||||
- {fileID: 11400000, guid: ec9d910e81be14a1484f351f20d32f6f, type: 2}
|
- {fileID: 11400000, guid: ec9d910e81be14a1484f351f20d32f6f, type: 2}
|
||||||
|
|||||||
8
Assets/AddressableAssetsData/Android.meta
Normal file
8
Assets/AddressableAssetsData/Android.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 9fb2ea5fa9e054d2daab312c3be4d727
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -21,6 +21,12 @@ MonoBehaviour:
|
|||||||
m_SerializedLabels:
|
m_SerializedLabels:
|
||||||
- drawing
|
- drawing
|
||||||
FlaggedDuringContentUpdateRestriction: 0
|
FlaggedDuringContentUpdateRestriction: 0
|
||||||
|
- m_GUID: 14477b5d35d0be9439ecb935f1c4e64e
|
||||||
|
m_Address: Donut
|
||||||
|
m_ReadOnly: 0
|
||||||
|
m_SerializedLabels:
|
||||||
|
- drawing
|
||||||
|
FlaggedDuringContentUpdateRestriction: 0
|
||||||
- m_GUID: 2043f692673a79543afed5cb879f0e04
|
- m_GUID: 2043f692673a79543afed5cb879f0e04
|
||||||
m_Address: Five
|
m_Address: Five
|
||||||
m_ReadOnly: 0
|
m_ReadOnly: 0
|
||||||
@@ -39,6 +45,24 @@ MonoBehaviour:
|
|||||||
m_SerializedLabels:
|
m_SerializedLabels:
|
||||||
- drawing
|
- drawing
|
||||||
FlaggedDuringContentUpdateRestriction: 0
|
FlaggedDuringContentUpdateRestriction: 0
|
||||||
|
- m_GUID: 4474b93704045a740ba9b8114e8a5238
|
||||||
|
m_Address: Fish
|
||||||
|
m_ReadOnly: 0
|
||||||
|
m_SerializedLabels:
|
||||||
|
- drawing
|
||||||
|
FlaggedDuringContentUpdateRestriction: 0
|
||||||
|
- m_GUID: 62ae112e11b695a40b889d773a36f8bd
|
||||||
|
m_Address: Elephant
|
||||||
|
m_ReadOnly: 0
|
||||||
|
m_SerializedLabels:
|
||||||
|
- drawing
|
||||||
|
FlaggedDuringContentUpdateRestriction: 0
|
||||||
|
- m_GUID: 6e36ba5d4763c694289c8ca75ee81449
|
||||||
|
m_Address: Bus
|
||||||
|
m_ReadOnly: 0
|
||||||
|
m_SerializedLabels:
|
||||||
|
- drawing
|
||||||
|
FlaggedDuringContentUpdateRestriction: 0
|
||||||
- m_GUID: 977dc7dac5ee6b543b8ed47c2299919e
|
- m_GUID: 977dc7dac5ee6b543b8ed47c2299919e
|
||||||
m_Address: Airplane
|
m_Address: Airplane
|
||||||
m_ReadOnly: 0
|
m_ReadOnly: 0
|
||||||
@@ -63,6 +87,12 @@ MonoBehaviour:
|
|||||||
m_SerializedLabels:
|
m_SerializedLabels:
|
||||||
- drawing
|
- drawing
|
||||||
FlaggedDuringContentUpdateRestriction: 0
|
FlaggedDuringContentUpdateRestriction: 0
|
||||||
|
- m_GUID: f3200c6715fea4a41bc71c0314b519cd
|
||||||
|
m_Address: Frog
|
||||||
|
m_ReadOnly: 0
|
||||||
|
m_SerializedLabels:
|
||||||
|
- drawing
|
||||||
|
FlaggedDuringContentUpdateRestriction: 0
|
||||||
m_ReadOnly: 0
|
m_ReadOnly: 0
|
||||||
m_Settings: {fileID: 11400000, guid: 4a94ef317c3674edd8270e4ed15031f6, type: 2}
|
m_Settings: {fileID: 11400000, guid: 4a94ef317c3674edd8270e4ed15031f6, type: 2}
|
||||||
m_SchemaSet:
|
m_SchemaSet:
|
||||||
|
|||||||
28
Assets/AddressableAssetsData/ProfileDataSourceSettings.asset
Normal file
28
Assets/AddressableAssetsData/ProfileDataSourceSettings.asset
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
%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: 7e3976da977cb49238499ea3b4c237ae, type: 3}
|
||||||
|
m_Name: ProfileDataSourceSettings
|
||||||
|
m_EditorClassIdentifier: Unity.Addressables.Editor::UnityEditor.AddressableAssets.Settings.ProfileDataSourceSettings
|
||||||
|
profileGroupTypes:
|
||||||
|
- m_GroupTypePrefix: Built-In
|
||||||
|
m_Variables:
|
||||||
|
- m_Suffix: BuildPath
|
||||||
|
m_Value: '[UnityEngine.AddressableAssets.Addressables.BuildPath]/[BuildTarget]'
|
||||||
|
- m_Suffix: LoadPath
|
||||||
|
m_Value: '{UnityEngine.AddressableAssets.Addressables.RuntimePath}/[BuildTarget]'
|
||||||
|
environments: []
|
||||||
|
currentEnvironment:
|
||||||
|
id:
|
||||||
|
projectId:
|
||||||
|
projectGenesisId:
|
||||||
|
name:
|
||||||
|
isDefault: 0
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 550878786d0cb4f48a8afa99c49a5635
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 11400000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -8,6 +8,5 @@ namespace Darkmatter.Core.Contracts.Features.GameplayFlow
|
|||||||
UniTask BackAsync(CancellationToken cancellationToken);
|
UniTask BackAsync(CancellationToken cancellationToken);
|
||||||
UniTask SaveAsync(CancellationToken cancellationToken);
|
UniTask SaveAsync(CancellationToken cancellationToken);
|
||||||
UniTask NextAsync(CancellationToken cancellationToken);
|
UniTask NextAsync(CancellationToken cancellationToken);
|
||||||
void OnApplicationPaused();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
8
Assets/Darkmatter/Code/Core/Contracts/Services/Ads.meta
Normal file
8
Assets/Darkmatter/Code/Core/Contracts/Services/Ads.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 683c2f491e8042d0a46bb7b5ad497be3
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using Cysharp.Threading.Tasks;
|
||||||
|
using Darkmatter.Core.Data.Dynamic.Services.Ads;
|
||||||
|
using Darkmatter.Core.Enums.Services.Ads;
|
||||||
|
|
||||||
|
namespace Darkmatter.Core.Contracts.Services.Ads
|
||||||
|
{
|
||||||
|
public interface IAdService
|
||||||
|
{
|
||||||
|
bool IsInitialized { get; }
|
||||||
|
event Action<AdFormat, AdLoadState> LoadStateChanged;
|
||||||
|
|
||||||
|
UniTask InitializeAsync(CancellationToken cancellationToken);
|
||||||
|
|
||||||
|
UniTask<bool> LoadAsync(AdFormat format, CancellationToken cancellationToken);
|
||||||
|
bool IsReady(AdFormat format);
|
||||||
|
UniTask<AdShowResult> ShowAsync(AdFormat format, CancellationToken cancellationToken);
|
||||||
|
|
||||||
|
UniTask<bool> ShowBannerAsync(BannerSize size, BannerPosition position, CancellationToken cancellationToken);
|
||||||
|
void HideBanner();
|
||||||
|
void DestroyBanner();
|
||||||
|
|
||||||
|
void SetConsent(bool hasUserConsent, bool isChildDirected);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ffaede2f106c45a68197d70ec929c7de
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 3d69da96f4ccb4de5910011cfc1e07a1
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Darkmatter.Core.Contracts.Services.Music
|
||||||
|
{
|
||||||
|
public interface IMusicService
|
||||||
|
{
|
||||||
|
void Play(AudioClip clip, float volume01 = 1f, bool loop = true);
|
||||||
|
void Stop(float fadeOutSeconds = 0f);
|
||||||
|
void SetVolume(float volume01);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: f851c65abe6f240a894610f67360fe58
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 868c1ce3a8b740a8aa4949fe685a1dcc
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
namespace Darkmatter.Core.Data.Dynamic.Services.Ads
|
||||||
|
{
|
||||||
|
public readonly struct AdReward
|
||||||
|
{
|
||||||
|
public readonly string Type;
|
||||||
|
public readonly double Amount;
|
||||||
|
|
||||||
|
public AdReward(string type, double amount)
|
||||||
|
{
|
||||||
|
Type = type;
|
||||||
|
Amount = amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 784af4bbe9df4c25b5d304d8364ade62
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
namespace Darkmatter.Core.Data.Dynamic.Services.Ads
|
||||||
|
{
|
||||||
|
public readonly struct AdShowResult
|
||||||
|
{
|
||||||
|
public readonly bool Shown;
|
||||||
|
public readonly bool Rewarded;
|
||||||
|
public readonly AdReward Reward;
|
||||||
|
public readonly string Error;
|
||||||
|
|
||||||
|
public AdShowResult(bool shown, bool rewarded, AdReward reward, string error)
|
||||||
|
{
|
||||||
|
Shown = shown;
|
||||||
|
Rewarded = rewarded;
|
||||||
|
Reward = reward;
|
||||||
|
Error = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AdShowResult Success() => new(true, false, default, null);
|
||||||
|
public static AdShowResult WithReward(AdReward reward) => new(true, true, reward, null);
|
||||||
|
public static AdShowResult Failure(string error) => new(false, false, default, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: e2859963572b4173957a583249f5d399
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: f69d14fcc0bdd4b93aeb1717d030a9ca
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
namespace Darkmatter.Core.Data.Signals.Features.Capture
|
||||||
|
{
|
||||||
|
public record struct GallerySaveCompletedSignal(bool Success);
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 04fbdae933fab4011879c232a1041042
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
namespace Darkmatter.Core.Data.Signals.Features.Capture
|
||||||
|
{
|
||||||
|
public record struct GallerySaveStartedSignal;
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: cff479845a55e4dd0a6d66043c91f661
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
|
using System;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace Darkmatter.Core
|
namespace Darkmatter.Core
|
||||||
{
|
{
|
||||||
public record struct OpenArtBookSignal;
|
public record struct OpenArtBookSignal(Action OnClose);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 4dcdb224825b4764a080e5b881b996e4
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Darkmatter.Core.Enums.Services.Ads;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Darkmatter.Core.Data.Static.Services.Ads
|
||||||
|
{
|
||||||
|
[CreateAssetMenu(fileName = "AdUnitCatalog", menuName = "Darkmatter/Ads/Ad Unit Catalog")]
|
||||||
|
public class AdUnitCatalogSO : ScriptableObject
|
||||||
|
{
|
||||||
|
[Serializable]
|
||||||
|
public class Entry
|
||||||
|
{
|
||||||
|
public AdFormat Format;
|
||||||
|
public string AndroidUnitId;
|
||||||
|
public string IosUnitId;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Header("App IDs")]
|
||||||
|
[SerializeField] private string androidAppId;
|
||||||
|
[SerializeField] private string iosAppId;
|
||||||
|
|
||||||
|
[Header("Test Mode")]
|
||||||
|
[Tooltip("If true, returns Google sample ad unit IDs (safe for development).")]
|
||||||
|
[SerializeField] private bool useTestUnits = true;
|
||||||
|
|
||||||
|
[Tooltip("Device IDs to treat as test devices (hashed IDs from logcat).")]
|
||||||
|
[SerializeField] private List<string> testDeviceIds = new();
|
||||||
|
|
||||||
|
[Header("Production Unit IDs")]
|
||||||
|
[SerializeField] private List<Entry> entries = new();
|
||||||
|
|
||||||
|
public string AndroidAppId => androidAppId;
|
||||||
|
public string IosAppId => iosAppId;
|
||||||
|
public bool UseTestUnits => useTestUnits;
|
||||||
|
public IReadOnlyList<string> TestDeviceIds => testDeviceIds;
|
||||||
|
|
||||||
|
public string GetUnitId(AdFormat format, RuntimePlatform platform)
|
||||||
|
{
|
||||||
|
if (useTestUnits || Application.isEditor)
|
||||||
|
{
|
||||||
|
return GetTestUnitId(format, platform);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var e in entries)
|
||||||
|
{
|
||||||
|
if (e == null || e.Format != format) continue;
|
||||||
|
return platform == RuntimePlatform.IPhonePlayer ? e.IosUnitId : e.AndroidUnitId;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetTestUnitId(AdFormat format, RuntimePlatform platform)
|
||||||
|
{
|
||||||
|
bool ios = platform == RuntimePlatform.IPhonePlayer;
|
||||||
|
return format switch
|
||||||
|
{
|
||||||
|
AdFormat.Banner => ios ? "ca-app-pub-3940256099942544/2934735716" : "ca-app-pub-3940256099942544/6300978111",
|
||||||
|
AdFormat.Interstitial => ios ? "ca-app-pub-3940256099942544/4411468910" : "ca-app-pub-3940256099942544/1033173712",
|
||||||
|
AdFormat.Rewarded => ios ? "ca-app-pub-3940256099942544/1712485313" : "ca-app-pub-3940256099942544/5224354917",
|
||||||
|
AdFormat.RewardedInterstitial => ios ? "ca-app-pub-3940256099942544/6978759866" : "ca-app-pub-3940256099942544/5354046379",
|
||||||
|
AdFormat.AppOpen => ios ? "ca-app-pub-3940256099942544/5575463023" : "ca-app-pub-3940256099942544/9257395921",
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: e6352f0162df4adf99a5945e25c6bf40
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
8
Assets/Darkmatter/Code/Core/Enums/Services/Ads.meta
Normal file
8
Assets/Darkmatter/Code/Core/Enums/Services/Ads.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 40846ca6e71249d68a99e1d226fd162d
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
11
Assets/Darkmatter/Code/Core/Enums/Services/Ads/AdFormat.cs
Normal file
11
Assets/Darkmatter/Code/Core/Enums/Services/Ads/AdFormat.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
namespace Darkmatter.Core.Enums.Services.Ads
|
||||||
|
{
|
||||||
|
public enum AdFormat
|
||||||
|
{
|
||||||
|
Banner,
|
||||||
|
Interstitial,
|
||||||
|
Rewarded,
|
||||||
|
RewardedInterstitial,
|
||||||
|
AppOpen
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ce1e736d19fc4569af80b06274dc3935
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace Darkmatter.Core.Enums.Services.Ads
|
||||||
|
{
|
||||||
|
public enum AdLoadState
|
||||||
|
{
|
||||||
|
Idle,
|
||||||
|
Loading,
|
||||||
|
Loaded,
|
||||||
|
Failed
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 44f09c3955b34814abfa83407118a6a5
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
namespace Darkmatter.Core.Enums.Services.Ads
|
||||||
|
{
|
||||||
|
public enum BannerPosition
|
||||||
|
{
|
||||||
|
Top,
|
||||||
|
Bottom,
|
||||||
|
TopLeft,
|
||||||
|
TopRight,
|
||||||
|
BottomLeft,
|
||||||
|
BottomRight,
|
||||||
|
Center
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 2223e7f2cedb4f479f7c6722e0ca7fd1
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
13
Assets/Darkmatter/Code/Core/Enums/Services/Ads/BannerSize.cs
Normal file
13
Assets/Darkmatter/Code/Core/Enums/Services/Ads/BannerSize.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
namespace Darkmatter.Core.Enums.Services.Ads
|
||||||
|
{
|
||||||
|
public enum BannerSize
|
||||||
|
{
|
||||||
|
Banner,
|
||||||
|
LargeBanner,
|
||||||
|
MediumRectangle,
|
||||||
|
FullBanner,
|
||||||
|
Leaderboard,
|
||||||
|
SmartBanner,
|
||||||
|
AnchoredAdaptive
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 570a6e9b47c5451c8ef78aa7a3923ead
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -7,5 +7,9 @@ namespace Darkmatter.Core.Enums.Services.Audio
|
|||||||
ShapeSnap = 101,
|
ShapeSnap = 101,
|
||||||
ShapeReturn = 102,
|
ShapeReturn = 102,
|
||||||
UiTap = 200,
|
UiTap = 200,
|
||||||
|
PlayButtonTap = 201,
|
||||||
|
LevelComplete = 300,
|
||||||
|
FireWorkLaunch = 400,
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ namespace Darkmatter.Features.Artbook
|
|||||||
private readonly List<ArtbookEntry> _entries = new();
|
private readonly List<ArtbookEntry> _entries = new();
|
||||||
private readonly List<Sprite> _ownedSprites = new();
|
private readonly List<Sprite> _ownedSprites = new();
|
||||||
private readonly List<Texture2D> _ownedTextures = new();
|
private readonly List<Texture2D> _ownedTextures = new();
|
||||||
|
private Action _onClose;
|
||||||
private CancellationTokenSource _cts;
|
private CancellationTokenSource _cts;
|
||||||
private IDisposable _openSubscription;
|
private IDisposable _openSubscription;
|
||||||
private int _currentSpread;
|
private int _currentSpread;
|
||||||
@@ -60,10 +60,12 @@ namespace Darkmatter.Features.Artbook
|
|||||||
|
|
||||||
private void HandleOpenArtbookEvent(OpenArtBookSignal signal)
|
private void HandleOpenArtbookEvent(OpenArtBookSignal signal)
|
||||||
{
|
{
|
||||||
|
_onClose = null;
|
||||||
_view.Show();
|
_view.Show();
|
||||||
_cts?.Cancel();
|
_cts?.Cancel();
|
||||||
_cts?.Dispose();
|
_cts?.Dispose();
|
||||||
_cts = new CancellationTokenSource();
|
_cts = new CancellationTokenSource();
|
||||||
|
_onClose = signal.OnClose;
|
||||||
LoadAndShowAsync(_cts.Token).Forget();
|
LoadAndShowAsync(_cts.Token).Forget();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,7 +166,7 @@ namespace Darkmatter.Features.Artbook
|
|||||||
|
|
||||||
private void HandleBackButtonClicked()
|
private void HandleBackButtonClicked()
|
||||||
{
|
{
|
||||||
_eventBus.Publish(new OpenColorBookSignal());
|
_onClose?.Invoke();
|
||||||
_view.Hide();
|
_view.Hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
"references": [
|
"references": [
|
||||||
"GUID:6a0a834eb41764f12ba55c3fb04a40cb",
|
"GUID:6a0a834eb41764f12ba55c3fb04a40cb",
|
||||||
"GUID:c1c03c0e5b2f4412b9f2be1c20d6a9b1",
|
"GUID:c1c03c0e5b2f4412b9f2be1c20d6a9b1",
|
||||||
|
"GUID:b4c9f7fbf1e144933a1797dc208ece5f",
|
||||||
"GUID:b0214a6008ed146ff8f122a6a9c2f6cc",
|
"GUID:b0214a6008ed146ff8f122a6a9c2f6cc",
|
||||||
"GUID:f51ebe6a0ceec4240a699833d6309b23"
|
"GUID:f51ebe6a0ceec4240a699833d6309b23"
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ namespace Darkmatter.Features.Capture
|
|||||||
{
|
{
|
||||||
[SerializeField, Range(0.1f, 2f)] private float captureScale = 1f;
|
[SerializeField, Range(0.1f, 2f)] private float captureScale = 1f;
|
||||||
[SerializeField] private CaptureButtonView captureButtonView;
|
[SerializeField] private CaptureButtonView captureButtonView;
|
||||||
|
[SerializeField] private GallerySaveView gallerySaveView;
|
||||||
|
|
||||||
public void Register(IContainerBuilder builder)
|
public void Register(IContainerBuilder builder)
|
||||||
{
|
{
|
||||||
@@ -19,6 +20,9 @@ namespace Darkmatter.Features.Capture
|
|||||||
|
|
||||||
if (captureButtonView != null)
|
if (captureButtonView != null)
|
||||||
builder.RegisterEntryPoint<CaptureButtonPresenter>().WithParameter(captureButtonView);
|
builder.RegisterEntryPoint<CaptureButtonPresenter>().WithParameter(captureButtonView);
|
||||||
|
|
||||||
|
if (gallerySaveView != null)
|
||||||
|
builder.RegisterEntryPoint<GallerySavePresenter>().WithParameter(gallerySaveView);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ using Darkmatter.Core.Contracts.Features.Capture;
|
|||||||
using Darkmatter.Core.Contracts.Features.GameplayFlow;
|
using Darkmatter.Core.Contracts.Features.GameplayFlow;
|
||||||
using Darkmatter.Core.Contracts.Services.Capture;
|
using Darkmatter.Core.Contracts.Services.Capture;
|
||||||
using Darkmatter.Core.Contracts.Services.Gallery;
|
using Darkmatter.Core.Contracts.Services.Gallery;
|
||||||
|
using Darkmatter.Core.Data.Signals.Features.Capture;
|
||||||
|
using Darkmatter.Libs.Observer;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace Darkmatter.Features.Capture
|
namespace Darkmatter.Features.Capture
|
||||||
@@ -14,17 +16,20 @@ namespace Darkmatter.Features.Capture
|
|||||||
private readonly ICaptureService _captureService;
|
private readonly ICaptureService _captureService;
|
||||||
private readonly IGalleryService _galleryService;
|
private readonly IGalleryService _galleryService;
|
||||||
private readonly IGameplaySceneRefs _refs;
|
private readonly IGameplaySceneRefs _refs;
|
||||||
|
private readonly IEventBus _bus;
|
||||||
private readonly CaptureConfig _config;
|
private readonly CaptureConfig _config;
|
||||||
|
|
||||||
public CaptureSystem(
|
public CaptureSystem(
|
||||||
ICaptureService captureService,
|
ICaptureService captureService,
|
||||||
IGalleryService galleryService,
|
IGalleryService galleryService,
|
||||||
IGameplaySceneRefs refs,
|
IGameplaySceneRefs refs,
|
||||||
|
IEventBus bus,
|
||||||
CaptureConfig config)
|
CaptureConfig config)
|
||||||
{
|
{
|
||||||
_captureService = captureService;
|
_captureService = captureService;
|
||||||
_galleryService = galleryService;
|
_galleryService = galleryService;
|
||||||
_refs = refs;
|
_refs = refs;
|
||||||
|
_bus = bus;
|
||||||
_config = config;
|
_config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,15 +39,21 @@ namespace Darkmatter.Features.Capture
|
|||||||
if (!saveToGallery || png == null || png.Length == 0) return png;
|
if (!saveToGallery || png == null || png.Length == 0) return png;
|
||||||
|
|
||||||
var tex = new Texture2D(2, 2, TextureFormat.RGBA32, mipChain: false);
|
var tex = new Texture2D(2, 2, TextureFormat.RGBA32, mipChain: false);
|
||||||
|
var success = false;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (tex.LoadImage(png))
|
if (tex.LoadImage(png))
|
||||||
|
{
|
||||||
|
_bus.Publish(new GallerySaveStartedSignal());
|
||||||
await _galleryService.SaveImageAsync(tex,
|
await _galleryService.SaveImageAsync(tex,
|
||||||
$"colorbook_{DateTime.UtcNow:yyyyMMdd_HHmmss}.png", ct);
|
$"colorbook_{DateTime.UtcNow:yyyyMMdd_HHmmss}.png", ct);
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
UnityEngine.Object.Destroy(tex);
|
UnityEngine.Object.Destroy(tex);
|
||||||
|
_bus.Publish(new GallerySaveCompletedSignal(success));
|
||||||
}
|
}
|
||||||
return png;
|
return png;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using Cysharp.Threading.Tasks;
|
||||||
|
using Darkmatter.Core.Data.Signals.Features.Capture;
|
||||||
|
using Darkmatter.Libs.Observer;
|
||||||
|
using VContainer.Unity;
|
||||||
|
|
||||||
|
namespace Darkmatter.Features.Capture.UI
|
||||||
|
{
|
||||||
|
public class GallerySavePresenter : IStartable, IDisposable
|
||||||
|
{
|
||||||
|
private readonly GallerySaveView _view;
|
||||||
|
private readonly IEventBus _bus;
|
||||||
|
|
||||||
|
private IDisposable _startedSub;
|
||||||
|
private IDisposable _completedSub;
|
||||||
|
private CancellationTokenSource _popupCts;
|
||||||
|
|
||||||
|
public GallerySavePresenter(GallerySaveView view, IEventBus bus)
|
||||||
|
{
|
||||||
|
_view = view;
|
||||||
|
_bus = bus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Start()
|
||||||
|
{
|
||||||
|
_startedSub = _bus.Subscribe<GallerySaveStartedSignal>(OnStarted);
|
||||||
|
_completedSub = _bus.Subscribe<GallerySaveCompletedSignal>(OnCompleted);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnStarted(GallerySaveStartedSignal _)
|
||||||
|
{
|
||||||
|
CancelPopup();
|
||||||
|
_view.ShowSaving();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCompleted(GallerySaveCompletedSignal signal)
|
||||||
|
{
|
||||||
|
_view.HideSaving();
|
||||||
|
if (!signal.Success) return;
|
||||||
|
CancelPopup();
|
||||||
|
_popupCts = new CancellationTokenSource();
|
||||||
|
ShowPopupAsync(_popupCts.Token).Forget();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async UniTaskVoid ShowPopupAsync(CancellationToken ct)
|
||||||
|
{
|
||||||
|
_view.ShowSuccess();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await UniTask.Delay(TimeSpan.FromSeconds(_view.PopupAutoHideSeconds), cancellationToken: ct);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException) { return; }
|
||||||
|
_view.HideSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CancelPopup()
|
||||||
|
{
|
||||||
|
_popupCts?.Cancel();
|
||||||
|
_popupCts?.Dispose();
|
||||||
|
_popupCts = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_startedSub?.Dispose();
|
||||||
|
_completedSub?.Dispose();
|
||||||
|
CancelPopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 71d8cccc7b3a3436ba83ca3683fbea9f
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Darkmatter.Features.Capture.UI
|
||||||
|
{
|
||||||
|
public class GallerySaveView : MonoBehaviour
|
||||||
|
{
|
||||||
|
[SerializeField] private GameObject savingIndicator;
|
||||||
|
[SerializeField] private GameObject successPopup;
|
||||||
|
[SerializeField, Min(0f)] private float popupAutoHideSeconds = 1.5f;
|
||||||
|
|
||||||
|
public float PopupAutoHideSeconds => popupAutoHideSeconds;
|
||||||
|
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
if (savingIndicator != null) savingIndicator.SetActive(false);
|
||||||
|
if (successPopup != null) successPopup.SetActive(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ShowSaving()
|
||||||
|
{
|
||||||
|
if (savingIndicator != null) savingIndicator.SetActive(true);
|
||||||
|
if (successPopup != null) successPopup.SetActive(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void HideSaving()
|
||||||
|
{
|
||||||
|
if (savingIndicator != null) savingIndicator.SetActive(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ShowSuccess()
|
||||||
|
{
|
||||||
|
if (successPopup != null) successPopup.SetActive(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void HideSuccess()
|
||||||
|
{
|
||||||
|
if (successPopup != null) successPopup.SetActive(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: bbcc5a579d90f4763a781379f5213d6e
|
||||||
@@ -6,8 +6,10 @@ using Darkmatter.Core;
|
|||||||
using Darkmatter.Core.Contracts.Features.DrawingCatalog;
|
using Darkmatter.Core.Contracts.Features.DrawingCatalog;
|
||||||
using Darkmatter.Core.Contracts.Features.Loading;
|
using Darkmatter.Core.Contracts.Features.Loading;
|
||||||
using Darkmatter.Core.Contracts.Features.Progression;
|
using Darkmatter.Core.Contracts.Features.Progression;
|
||||||
|
using Darkmatter.Core.Contracts.Services.Ads;
|
||||||
using Darkmatter.Core.Contracts.Services.Scenes;
|
using Darkmatter.Core.Contracts.Services.Scenes;
|
||||||
using Darkmatter.Core.Data.Signals.Features.Drawing;
|
using Darkmatter.Core.Data.Signals.Features.Drawing;
|
||||||
|
using Darkmatter.Core.Enums.Services.Ads;
|
||||||
using Darkmatter.Core.Enums.Services.Scenes;
|
using Darkmatter.Core.Enums.Services.Scenes;
|
||||||
using Darkmatter.Libs.Observer;
|
using Darkmatter.Libs.Observer;
|
||||||
using VContainer.Unity;
|
using VContainer.Unity;
|
||||||
@@ -20,33 +22,54 @@ public class ColorbookFlowController : IAsyncStartable, IDisposable
|
|||||||
private readonly ILoadingScreen _loadingScreen;
|
private readonly ILoadingScreen _loadingScreen;
|
||||||
private readonly IProgressionSystem _progression;
|
private readonly IProgressionSystem _progression;
|
||||||
private readonly ISceneService _scenes;
|
private readonly ISceneService _scenes;
|
||||||
|
private readonly IAdService _ads;
|
||||||
private readonly IEventBus _bus;
|
private readonly IEventBus _bus;
|
||||||
|
|
||||||
private IDisposable _selectedSub;
|
private IDisposable _selectedSub;
|
||||||
private IDisposable _returnToMainMenuSubscription;
|
private IDisposable _returnToMainMenuSubscription;
|
||||||
private bool _navigatingToGameplay;
|
private bool _navigatingToGameplay;
|
||||||
|
private CancellationTokenSource _scopeCts;
|
||||||
|
|
||||||
public ColorbookFlowController(
|
public ColorbookFlowController(
|
||||||
IDrawingCatalogController drawingCatalog,
|
IDrawingCatalogController drawingCatalog,
|
||||||
ILoadingScreen loadingScreen,
|
ILoadingScreen loadingScreen,
|
||||||
IProgressionSystem progression,
|
IProgressionSystem progression,
|
||||||
ISceneService scenes,
|
ISceneService scenes,
|
||||||
|
IAdService ads,
|
||||||
IEventBus bus)
|
IEventBus bus)
|
||||||
{
|
{
|
||||||
_drawingCatalog = drawingCatalog;
|
_drawingCatalog = drawingCatalog;
|
||||||
_loadingScreen = loadingScreen;
|
_loadingScreen = loadingScreen;
|
||||||
_progression = progression;
|
_progression = progression;
|
||||||
_scenes = scenes;
|
_scenes = scenes;
|
||||||
|
_ads = ads;
|
||||||
_bus = bus;
|
_bus = bus;
|
||||||
_selectedSub = _bus.Subscribe<DrawingSelectedSignal>(OnDrawingSelected);
|
_selectedSub = _bus.Subscribe<DrawingSelectedSignal>(OnDrawingSelected);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async UniTask StartAsync(CancellationToken cancellation = new CancellationToken())
|
public async UniTask StartAsync(CancellationToken cancellation = new CancellationToken())
|
||||||
{
|
{
|
||||||
|
_scopeCts = CancellationTokenSource.CreateLinkedTokenSource(cancellation);
|
||||||
_returnToMainMenuSubscription = _bus.Subscribe<ReturnToMainMenuSignal>(OnReturnToMainMenu);
|
_returnToMainMenuSubscription = _bus.Subscribe<ReturnToMainMenuSignal>(OnReturnToMainMenu);
|
||||||
_loadingScreen.SetProgress(1f);
|
_loadingScreen.SetProgress(1f);
|
||||||
await _drawingCatalog.InitializeAsync(cancellation);
|
await _drawingCatalog.InitializeAsync(cancellation);
|
||||||
if (!_navigatingToGameplay) _loadingScreen.Hide();
|
if (!_navigatingToGameplay) _loadingScreen.Hide();
|
||||||
|
|
||||||
|
PrewarmRewardedAdAsync(_scopeCts.Token).Forget();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async UniTaskVoid PrewarmRewardedAdAsync(CancellationToken ct)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!_ads.IsInitialized) await _ads.InitializeAsync(ct);
|
||||||
|
if (!_ads.IsReady(AdFormat.Rewarded)) await _ads.LoadAsync(AdFormat.Rewarded, ct);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException) { }
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
UnityEngine.Debug.LogWarning($"[ColorbookFlow] Rewarded prewarm failed: {ex.Message}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnReturnToMainMenu(ReturnToMainMenuSignal signal)
|
private void OnReturnToMainMenu(ReturnToMainMenuSignal signal)
|
||||||
@@ -66,13 +89,20 @@ public class ColorbookFlowController : IAsyncStartable, IDisposable
|
|||||||
|
|
||||||
private void OnDrawingSelected(DrawingSelectedSignal signal)
|
private void OnDrawingSelected(DrawingSelectedSignal signal)
|
||||||
{
|
{
|
||||||
|
if (_navigatingToGameplay) return;
|
||||||
_navigatingToGameplay = true;
|
_navigatingToGameplay = true;
|
||||||
HandleSelectionAsync(signal.TemplateId).Forget();
|
HandleSelectionAsync(signal.TemplateId).Forget();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async UniTaskVoid HandleSelectionAsync(string templateId)
|
private async UniTaskVoid HandleSelectionAsync(string templateId)
|
||||||
{
|
{
|
||||||
|
var ct = _scopeCts?.Token ?? CancellationToken.None;
|
||||||
|
|
||||||
_loadingScreen.Show();
|
_loadingScreen.Show();
|
||||||
|
_loadingScreen.SetProgress(0f);
|
||||||
|
|
||||||
|
await ShowRewardedAdAsync(ct);
|
||||||
|
|
||||||
var progress = new Progress<float>(p => _loadingScreen.SetProgress(p * 0.5f));
|
var progress = new Progress<float>(p => _loadingScreen.SetProgress(p * 0.5f));
|
||||||
var mappedProgress = new Progress<float>(p => _loadingScreen.SetProgress(0.5f + p * 0.25f));
|
var mappedProgress = new Progress<float>(p => _loadingScreen.SetProgress(0.5f + p * 0.25f));
|
||||||
await _progression.SetLastOpenedAsync(templateId);
|
await _progression.SetLastOpenedAsync(templateId);
|
||||||
@@ -81,9 +111,34 @@ public class ColorbookFlowController : IAsyncStartable, IDisposable
|
|||||||
cancellationToken: default);
|
cancellationToken: default);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async UniTask ShowRewardedAdAsync(CancellationToken ct)
|
||||||
|
{
|
||||||
|
const int InitTimeoutMs = 4000;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!_ads.IsInitialized)
|
||||||
|
{
|
||||||
|
using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||||
|
timeoutCts.CancelAfter(InitTimeoutMs);
|
||||||
|
await _ads.InitializeAsync(timeoutCts.Token);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_ads.IsReady(AdFormat.Rewarded)) return;
|
||||||
|
|
||||||
|
await _ads.ShowAsync(AdFormat.Rewarded, ct);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException) { }
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
UnityEngine.Debug.LogWarning($"[ColorbookFlow] Rewarded ad skipped: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_selectedSub?.Dispose();
|
_selectedSub?.Dispose();
|
||||||
_returnToMainMenuSubscription?.Dispose();
|
_returnToMainMenuSubscription?.Dispose();
|
||||||
|
_scopeCts?.Cancel();
|
||||||
|
_scopeCts?.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -31,7 +31,8 @@ public class ColoringController : IColoringController, IDisposable
|
|||||||
|
|
||||||
private GameObject _colorInstance;
|
private GameObject _colorInstance;
|
||||||
private GameObject _colorButtonPrefab;
|
private GameObject _colorButtonPrefab;
|
||||||
private GameObject _completionAnimationPrefab;
|
private GameObject _completionAnimationInstance;
|
||||||
|
private CompletionAnimationView _completionAnimationView;
|
||||||
private readonly List<ColorRegionView> _regions = new();
|
private readonly List<ColorRegionView> _regions = new();
|
||||||
private readonly List<ColorButton> _buttons = new();
|
private readonly List<ColorButton> _buttons = new();
|
||||||
private readonly Dictionary<string, Color> _authoredColors = new();
|
private readonly Dictionary<string, Color> _authoredColors = new();
|
||||||
@@ -89,17 +90,17 @@ public class ColoringController : IColoringController, IDisposable
|
|||||||
|
|
||||||
public async UniTask PlayCompletionAnimationAsync(CancellationToken ct)
|
public async UniTask PlayCompletionAnimationAsync(CancellationToken ct)
|
||||||
{
|
{
|
||||||
if (_completionAnimationPrefab == null) return;
|
if (_completionAnimationInstance == null || _completionAnimationView == null) return;
|
||||||
var instance = UnityEngine.Object.Instantiate(_completionAnimationPrefab, _refs.PaperRoot);
|
if (_colorInstance != null) _colorInstance.SetActive(false);
|
||||||
|
_completionAnimationInstance.SetActive(true);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var view = instance.GetComponentInChildren<CompletionAnimationView>();
|
await _completionAnimationView.PlayAsync(ct);
|
||||||
if (view == null) return;
|
|
||||||
await view.PlayAsync(ct);
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (instance != null) UnityEngine.Object.Destroy(instance);
|
if (_completionAnimationInstance != null)
|
||||||
|
_completionAnimationInstance.SetActive(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,7 +141,13 @@ public class ColoringController : IColoringController, IDisposable
|
|||||||
|
|
||||||
_regions.Clear();
|
_regions.Clear();
|
||||||
_authoredColors.Clear();
|
_authoredColors.Clear();
|
||||||
_completionAnimationPrefab = null;
|
|
||||||
|
if (_completionAnimationInstance != null)
|
||||||
|
{
|
||||||
|
UnityEngine.Object.Destroy(_completionAnimationInstance);
|
||||||
|
_completionAnimationInstance = null;
|
||||||
|
_completionAnimationView = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (_colorInstance != null)
|
if (_colorInstance != null)
|
||||||
{
|
{
|
||||||
@@ -154,7 +161,12 @@ public class ColoringController : IColoringController, IDisposable
|
|||||||
private void InitializeColorRegions(IDrawingTemplate template, IReadOnlyDictionary<string, Color> savedColors)
|
private void InitializeColorRegions(IDrawingTemplate template, IReadOnlyDictionary<string, Color> savedColors)
|
||||||
{
|
{
|
||||||
_colorInstance = UnityEngine.Object.Instantiate(template.ColoringPrefab, _refs.PaperRoot);
|
_colorInstance = UnityEngine.Object.Instantiate(template.ColoringPrefab, _refs.PaperRoot);
|
||||||
_completionAnimationPrefab = template.CompletionAnimationPrefab;
|
if (template.CompletionAnimationPrefab != null)
|
||||||
|
{
|
||||||
|
_completionAnimationInstance = UnityEngine.Object.Instantiate(template.CompletionAnimationPrefab, _refs.PaperRoot);
|
||||||
|
_completionAnimationView = _completionAnimationInstance.GetComponentInChildren<CompletionAnimationView>(includeInactive: true);
|
||||||
|
_completionAnimationInstance.SetActive(false);
|
||||||
|
}
|
||||||
var views = _colorInstance.GetComponentsInChildren<ColorRegionView>(includeInactive: true);
|
var views = _colorInstance.GetComponentsInChildren<ColorRegionView>(includeInactive: true);
|
||||||
|
|
||||||
foreach (var region in views)
|
foreach (var region in views)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using Darkmatter.Core;
|
||||||
using Darkmatter.Core.Data.Signals.Features.ShapeBuilder;
|
using Darkmatter.Core.Data.Signals.Features.ShapeBuilder;
|
||||||
using Darkmatter.Libs.Observer;
|
using Darkmatter.Libs.Observer;
|
||||||
using VContainer.Unity;
|
using VContainer.Unity;
|
||||||
@@ -22,11 +23,18 @@ namespace Darkmatter.Features.Coloring.UI
|
|||||||
{
|
{
|
||||||
_view.HideInstant();
|
_view.HideInstant();
|
||||||
_assembledSub = _bus.Subscribe<ShapeAssembledSignal>(_ => _view.Show());
|
_assembledSub = _bus.Subscribe<ShapeAssembledSignal>(_ => _view.Show());
|
||||||
|
_view.OnArtbookClicked += HandleArtbookClicked;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleArtbookClicked()
|
||||||
|
{
|
||||||
|
_bus.Publish(new OpenArtBookSignal());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_assembledSub?.Dispose();
|
_assembledSub?.Dispose();
|
||||||
|
_view.OnArtbookClicked -= HandleArtbookClicked;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
using System;
|
||||||
using PrimeTween;
|
using PrimeTween;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
|
||||||
namespace Darkmatter.Features.Coloring.UI
|
namespace Darkmatter.Features.Coloring.UI
|
||||||
{
|
{
|
||||||
@@ -8,6 +10,7 @@ namespace Darkmatter.Features.Coloring.UI
|
|||||||
{
|
{
|
||||||
[SerializeField] private RectTransform spawnRoot;
|
[SerializeField] private RectTransform spawnRoot;
|
||||||
[SerializeField] private RectTransform animatedRoot;
|
[SerializeField] private RectTransform animatedRoot;
|
||||||
|
[SerializeField] private Button artbookButton;
|
||||||
[SerializeField] private float showDuration = 0.35f;
|
[SerializeField] private float showDuration = 0.35f;
|
||||||
[SerializeField] private float hideDuration = 0.25f;
|
[SerializeField] private float hideDuration = 0.25f;
|
||||||
[SerializeField] private Vector2 hiddenOffset = new(1500f, 0f);
|
[SerializeField] private Vector2 hiddenOffset = new(1500f, 0f);
|
||||||
@@ -17,9 +20,16 @@ namespace Darkmatter.Features.Coloring.UI
|
|||||||
private Sequence _activeSequence;
|
private Sequence _activeSequence;
|
||||||
private bool _refsReady;
|
private bool _refsReady;
|
||||||
|
|
||||||
|
public event Action OnArtbookClicked;
|
||||||
public RectTransform SpawnRoot => spawnRoot;
|
public RectTransform SpawnRoot => spawnRoot;
|
||||||
|
|
||||||
private void Awake() => EnsureRefs();
|
private void Awake()
|
||||||
|
{
|
||||||
|
EnsureRefs();
|
||||||
|
if (artbookButton != null) artbookButton.onClick.AddListener(HandleArtbookClicked);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleArtbookClicked() => OnArtbookClicked?.Invoke();
|
||||||
|
|
||||||
private void EnsureRefs()
|
private void EnsureRefs()
|
||||||
{
|
{
|
||||||
@@ -80,5 +90,10 @@ namespace Darkmatter.Features.Coloring.UI
|
|||||||
{
|
{
|
||||||
KillActive();
|
KillActive();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnDestroy()
|
||||||
|
{
|
||||||
|
if (artbookButton != null) artbookButton.onClick.RemoveListener(HandleArtbookClicked);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,17 +8,28 @@ namespace Darkmatter.Features.Coloring.UI
|
|||||||
public class CompletionAnimationView : MonoBehaviour
|
public class CompletionAnimationView : MonoBehaviour
|
||||||
{
|
{
|
||||||
[SerializeField] private RectTransform target;
|
[SerializeField] private RectTransform target;
|
||||||
[SerializeField] private float duration = 0.6f;
|
[SerializeField] private float duration = 1f;
|
||||||
[SerializeField] private Vector3 startScale = Vector3.zero;
|
[SerializeField] private Vector3 startScale = Vector3.zero;
|
||||||
[SerializeField] private Vector3 endScale = Vector3.one;
|
[SerializeField] private Vector3 endScale = Vector3.one;
|
||||||
[SerializeField] private Ease ease = Ease.OutBack;
|
[SerializeField] private Ease ease = Ease.OutBack;
|
||||||
|
|
||||||
|
[Header("Wiggle")]
|
||||||
|
[SerializeField] private float wiggleAngle = 10f;
|
||||||
|
[SerializeField] private float wiggleDuration = 0.25f;
|
||||||
|
[SerializeField, Min(1)] private int wiggleCycles = 3;
|
||||||
|
|
||||||
public async UniTask PlayAsync(CancellationToken ct)
|
public async UniTask PlayAsync(CancellationToken ct)
|
||||||
{
|
{
|
||||||
var rt = target != null ? target : transform as RectTransform;
|
var rt = target != null ? target : transform as RectTransform;
|
||||||
if (rt == null) return;
|
if (rt == null) return;
|
||||||
rt.localScale = startScale;
|
rt.localScale = startScale;
|
||||||
|
rt.localRotation = Quaternion.identity;
|
||||||
await Tween.Scale(rt, endScale, duration, ease).ToUniTask(cancellationToken: ct);
|
await Tween.Scale(rt, endScale, duration, ease).ToUniTask(cancellationToken: ct);
|
||||||
|
|
||||||
|
await Tween.LocalRotation(rt, new Vector3(0f, 0f, wiggleAngle), wiggleDuration, Ease.InOutSine,
|
||||||
|
cycles: wiggleCycles * 2, cycleMode: CycleMode.Yoyo)
|
||||||
|
.ToUniTask(cancellationToken: ct);
|
||||||
|
rt.localRotation = Quaternion.identity;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDestroy()
|
private void OnDestroy()
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
"GUID:c1c03c0e5b2f4412b9f2be1c20d6a9b1",
|
"GUID:c1c03c0e5b2f4412b9f2be1c20d6a9b1",
|
||||||
"GUID:b4c9f7fbf1e144933a1797dc208ece5f",
|
"GUID:b4c9f7fbf1e144933a1797dc208ece5f",
|
||||||
"GUID:b0214a6008ed146ff8f122a6a9c2f6cc",
|
"GUID:b0214a6008ed146ff8f122a6a9c2f6cc",
|
||||||
"GUID:f51ebe6a0ceec4240a699833d6309b23"
|
"GUID:f51ebe6a0ceec4240a699833d6309b23",
|
||||||
|
"GUID:6055be8ebefd69e48b49212b09b47b2f"
|
||||||
],
|
],
|
||||||
"includePlatforms": [],
|
"includePlatforms": [],
|
||||||
"excludePlatforms": [],
|
"excludePlatforms": [],
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ namespace Darkmatter.Features.DrawingCatalog
|
|||||||
_view.OnArtBookClicked += OnArtBookBtnClicked;
|
_view.OnArtBookClicked += OnArtBookBtnClicked;
|
||||||
_view.OnLeftPageClicked += OnLeftPageBtnClicked;
|
_view.OnLeftPageClicked += OnLeftPageBtnClicked;
|
||||||
_view.OnRightPageClicked += OnRightPageBtnClicked;
|
_view.OnRightPageClicked += OnRightPageBtnClicked;
|
||||||
|
_view.OnPageChangedByScroll += OnPageChangedByScroll;
|
||||||
|
_view.OnPageNumberClicked += OnPageNumberClicked;
|
||||||
|
|
||||||
_controller.ListChanged += OnListChanged;
|
_controller.ListChanged += OnListChanged;
|
||||||
|
|
||||||
@@ -68,9 +70,21 @@ namespace Darkmatter.Features.DrawingCatalog
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnPageChangedByScroll(int page)
|
||||||
|
{
|
||||||
|
_currentPage = page;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPageNumberClicked(int page)
|
||||||
|
{
|
||||||
|
if (page < 0 || page >= _totalPages || page == _currentPage) return;
|
||||||
|
_currentPage = page;
|
||||||
|
_view.SetPagination(_currentPage, _totalPages);
|
||||||
|
}
|
||||||
|
|
||||||
private void OnArtBookBtnClicked()
|
private void OnArtBookBtnClicked()
|
||||||
{
|
{
|
||||||
_eventBus.Publish(new OpenArtBookSignal());
|
_eventBus.Publish(new OpenArtBookSignal(()=> _view.Show()));
|
||||||
_view.Hide();
|
_view.Hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,6 +130,8 @@ namespace Darkmatter.Features.DrawingCatalog
|
|||||||
_view.OnArtBookClicked -= OnArtBookBtnClicked;
|
_view.OnArtBookClicked -= OnArtBookBtnClicked;
|
||||||
_view.OnLeftPageClicked -= OnLeftPageBtnClicked;
|
_view.OnLeftPageClicked -= OnLeftPageBtnClicked;
|
||||||
_view.OnRightPageClicked -= OnRightPageBtnClicked;
|
_view.OnRightPageClicked -= OnRightPageBtnClicked;
|
||||||
|
_view.OnPageChangedByScroll -= OnPageChangedByScroll;
|
||||||
|
_view.OnPageNumberClicked -= OnPageNumberClicked;
|
||||||
_openSubscription?.Dispose();
|
_openSubscription?.Dispose();
|
||||||
_cts.Cancel();
|
_cts.Cancel();
|
||||||
_cts.Dispose();
|
_cts.Dispose();
|
||||||
|
|||||||
@@ -20,15 +20,25 @@ namespace Darkmatter.Features.DrawingCatalog
|
|||||||
[SerializeField] private ScrollRect scrollRect;
|
[SerializeField] private ScrollRect scrollRect;
|
||||||
[SerializeField] private Button leftPageButton;
|
[SerializeField] private Button leftPageButton;
|
||||||
[SerializeField] private Button rightPageButton;
|
[SerializeField] private Button rightPageButton;
|
||||||
|
|
||||||
|
[Header("Page Numbers")]
|
||||||
|
[SerializeField] private RectTransform pageNumberContainer;
|
||||||
|
[SerializeField] private PageNumberButton pageNumberPrefab;
|
||||||
private readonly List<DrawingCatalogButton> _buttons = new();
|
private readonly List<DrawingCatalogButton> _buttons = new();
|
||||||
|
private readonly List<PageNumberButton> _pageButtons = new();
|
||||||
|
|
||||||
public event Action<string> OnItemClicked;
|
public event Action<string> OnItemClicked;
|
||||||
public event Action OnBackClicked;
|
public event Action OnBackClicked;
|
||||||
public event Action OnArtBookClicked;
|
public event Action OnArtBookClicked;
|
||||||
public event Action OnLeftPageClicked;
|
public event Action OnLeftPageClicked;
|
||||||
public event Action OnRightPageClicked;
|
public event Action OnRightPageClicked;
|
||||||
|
public event Action<int> OnPageChangedByScroll;
|
||||||
|
public event Action<int> OnPageNumberClicked;
|
||||||
|
|
||||||
private Coroutine _sliderCoroutine;
|
private Coroutine _sliderCoroutine;
|
||||||
|
private int _currentPage;
|
||||||
|
private int _totalPages = 1;
|
||||||
|
private bool _suppressScrollEvents;
|
||||||
|
|
||||||
public void Start()
|
public void Start()
|
||||||
{
|
{
|
||||||
@@ -36,6 +46,7 @@ namespace Darkmatter.Features.DrawingCatalog
|
|||||||
artBookButton.onClick.AddListener(() => OnArtBookClicked?.Invoke());
|
artBookButton.onClick.AddListener(() => OnArtBookClicked?.Invoke());
|
||||||
leftPageButton.onClick.AddListener(() => OnLeftPageClicked?.Invoke());
|
leftPageButton.onClick.AddListener(() => OnLeftPageClicked?.Invoke());
|
||||||
rightPageButton.onClick.AddListener(() => OnRightPageClicked?.Invoke());
|
rightPageButton.onClick.AddListener(() => OnRightPageClicked?.Invoke());
|
||||||
|
scrollRect.onValueChanged.AddListener(OnScrollValueChanged);
|
||||||
}
|
}
|
||||||
public void Show()
|
public void Show()
|
||||||
{
|
{
|
||||||
@@ -69,8 +80,11 @@ namespace Darkmatter.Features.DrawingCatalog
|
|||||||
|
|
||||||
public void SetPagination(int currentPage, int totalPages)
|
public void SetPagination(int currentPage, int totalPages)
|
||||||
{
|
{
|
||||||
leftPageButton.interactable = currentPage > 0;
|
_currentPage = currentPage;
|
||||||
rightPageButton.interactable = currentPage < totalPages - 1;
|
_totalPages = totalPages;
|
||||||
|
BuildPageNumbers(totalPages);
|
||||||
|
UpdateArrowButtons();
|
||||||
|
UpdateActivePageNumber();
|
||||||
if (totalPages <= 1) return;
|
if (totalPages <= 1) return;
|
||||||
|
|
||||||
float pageWidth = scrollRect.viewport.rect.width;
|
float pageWidth = scrollRect.viewport.rect.width;
|
||||||
@@ -84,8 +98,65 @@ namespace Darkmatter.Features.DrawingCatalog
|
|||||||
_sliderCoroutine = StartCoroutine(SmoothScrollTo(targetPosition));
|
_sliderCoroutine = StartCoroutine(SmoothScrollTo(targetPosition));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Keeps the arrow buttons (and the presenter) in sync when the user scrolls manually.
|
||||||
|
private void OnScrollValueChanged(Vector2 _)
|
||||||
|
{
|
||||||
|
if (_suppressScrollEvents || _totalPages <= 1) return;
|
||||||
|
|
||||||
|
int page = PageFromScrollPosition();
|
||||||
|
if (page == _currentPage) return;
|
||||||
|
|
||||||
|
_currentPage = page;
|
||||||
|
UpdateArrowButtons();
|
||||||
|
UpdateActivePageNumber();
|
||||||
|
OnPageChangedByScroll?.Invoke(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BuildPageNumbers(int totalPages)
|
||||||
|
{
|
||||||
|
if (pageNumberPrefab == null || pageNumberContainer == null) return;
|
||||||
|
|
||||||
|
while (_pageButtons.Count < totalPages)
|
||||||
|
{
|
||||||
|
var button = Instantiate(pageNumberPrefab, pageNumberContainer);
|
||||||
|
int pageIndex = _pageButtons.Count;
|
||||||
|
button.Initialize(pageIndex, () => OnPageNumberClicked?.Invoke(pageIndex));
|
||||||
|
_pageButtons.Add(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (_pageButtons.Count > totalPages)
|
||||||
|
{
|
||||||
|
var last = _pageButtons[^1];
|
||||||
|
_pageButtons.RemoveAt(_pageButtons.Count - 1);
|
||||||
|
Destroy(last.gameObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateActivePageNumber()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _pageButtons.Count; i++)
|
||||||
|
_pageButtons[i].SetHighlighted(i == _currentPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int PageFromScrollPosition()
|
||||||
|
{
|
||||||
|
float pageWidth = scrollRect.viewport.rect.width;
|
||||||
|
float maxScroll = content.rect.width - pageWidth;
|
||||||
|
if (maxScroll <= 0f) return 0;
|
||||||
|
|
||||||
|
int page = Mathf.RoundToInt(scrollRect.horizontalNormalizedPosition * maxScroll / pageWidth);
|
||||||
|
return Mathf.Clamp(page, 0, _totalPages - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateArrowButtons()
|
||||||
|
{
|
||||||
|
leftPageButton.interactable = _currentPage > 0;
|
||||||
|
rightPageButton.interactable = _currentPage < _totalPages - 1;
|
||||||
|
}
|
||||||
|
|
||||||
private System.Collections.IEnumerator SmoothScrollTo(float targetPosition)
|
private System.Collections.IEnumerator SmoothScrollTo(float targetPosition)
|
||||||
{
|
{
|
||||||
|
_suppressScrollEvents = true;
|
||||||
const float duration = 0.3f;
|
const float duration = 0.3f;
|
||||||
float elapsed = 0f;
|
float elapsed = 0f;
|
||||||
float startPosition = scrollRect.horizontalNormalizedPosition;
|
float startPosition = scrollRect.horizontalNormalizedPosition;
|
||||||
@@ -96,12 +167,15 @@ namespace Darkmatter.Features.DrawingCatalog
|
|||||||
yield return null;
|
yield return null;
|
||||||
}
|
}
|
||||||
scrollRect.horizontalNormalizedPosition = targetPosition;
|
scrollRect.horizontalNormalizedPosition = targetPosition;
|
||||||
|
_suppressScrollEvents = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void ResetScrollPosition()
|
public void ResetScrollPosition()
|
||||||
{
|
{
|
||||||
|
_suppressScrollEvents = true;
|
||||||
scrollRect.horizontalNormalizedPosition = 0f;
|
scrollRect.horizontalNormalizedPosition = 0f;
|
||||||
|
_suppressScrollEvents = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
using TMPro;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
|
||||||
|
namespace Darkmatter.Features.DrawingCatalog
|
||||||
|
{
|
||||||
|
public class PageNumberButton : MonoBehaviour
|
||||||
|
{
|
||||||
|
[SerializeField] private TMP_Text label;
|
||||||
|
[SerializeField] private Button button;
|
||||||
|
|
||||||
|
[Header("Highlight")]
|
||||||
|
[Tooltip("Image faded for unselected pages. Use the button's color block (below) to match the disabled look.")]
|
||||||
|
[SerializeField] private Image image;
|
||||||
|
[Tooltip("When on, unselected pages use the button's Disabled Color and selected use Normal Color. " +
|
||||||
|
"When off, only the alpha below is applied.")]
|
||||||
|
[SerializeField] private bool useButtonColors = true;
|
||||||
|
[Range(0f, 1f)]
|
||||||
|
[SerializeField] private float unselectedAlpha = 0.5f;
|
||||||
|
|
||||||
|
public int PageIndex { get; private set; }
|
||||||
|
|
||||||
|
public void Initialize(int pageIndex, UnityEngine.Events.UnityAction onClick)
|
||||||
|
{
|
||||||
|
PageIndex = pageIndex;
|
||||||
|
if (label != null) label.text = (pageIndex + 1).ToString();
|
||||||
|
button.onClick.RemoveAllListeners();
|
||||||
|
button.onClick.AddListener(onClick);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetHighlighted(bool isActive)
|
||||||
|
{
|
||||||
|
if (image == null) return;
|
||||||
|
|
||||||
|
if (useButtonColors)
|
||||||
|
{
|
||||||
|
var colors = button.colors;
|
||||||
|
image.color = isActive ? colors.normalColor : colors.disabledColor;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var c = image.color;
|
||||||
|
c.a = isActive ? 1f : unselectedAlpha;
|
||||||
|
image.color = c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 19495f2daba89ce44abe376a40ca313d
|
||||||
@@ -9,13 +9,14 @@ using Darkmatter.Core.Contracts.Features.GameplayFlow;
|
|||||||
using Darkmatter.Core.Contracts.Features.Loading;
|
using Darkmatter.Core.Contracts.Features.Loading;
|
||||||
using Darkmatter.Core.Contracts.Features.Progression;
|
using Darkmatter.Core.Contracts.Features.Progression;
|
||||||
using Darkmatter.Core.Contracts.Features.ShapeBuilder;
|
using Darkmatter.Core.Contracts.Features.ShapeBuilder;
|
||||||
using Darkmatter.Core.Contracts.Services.Gallery;
|
using Darkmatter.Core.Contracts.Services.Audio;
|
||||||
using Darkmatter.Core.Contracts.Services.Scenes;
|
using Darkmatter.Core.Contracts.Services.Scenes;
|
||||||
using Darkmatter.Core.Data.Dynamic.Features.Progression;
|
using Darkmatter.Core.Data.Dynamic.Features.Progression;
|
||||||
using Darkmatter.Core.Data.Signals.Features.Coloring;
|
using Darkmatter.Core.Data.Signals.Features.Coloring;
|
||||||
using Darkmatter.Core.Data.Signals.Features.Drawing;
|
using Darkmatter.Core.Data.Signals.Features.Drawing;
|
||||||
using Darkmatter.Core.Data.Signals.Features.ShapeBuilder;
|
using Darkmatter.Core.Data.Signals.Features.ShapeBuilder;
|
||||||
using Darkmatter.Core.Enums.Features.Progression;
|
using Darkmatter.Core.Enums.Features.Progression;
|
||||||
|
using Darkmatter.Core.Enums.Services.Audio;
|
||||||
using Darkmatter.Core.Enums.Services.Scenes;
|
using Darkmatter.Core.Enums.Services.Scenes;
|
||||||
using Darkmatter.Libs.Observer;
|
using Darkmatter.Libs.Observer;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
@@ -35,6 +36,7 @@ namespace Darkmatter.Features.GameplayFlow.Systems
|
|||||||
private readonly ICaptureFeature _capture;
|
private readonly ICaptureFeature _capture;
|
||||||
private readonly ILoadingScreen _loadingScreen;
|
private readonly ILoadingScreen _loadingScreen;
|
||||||
private readonly IGameplaySceneRefs _refs;
|
private readonly IGameplaySceneRefs _refs;
|
||||||
|
private readonly ISfxPlayer _sfx;
|
||||||
private readonly IEventBus _bus;
|
private readonly IEventBus _bus;
|
||||||
|
|
||||||
private IDrawingTemplate _template;
|
private IDrawingTemplate _template;
|
||||||
@@ -43,6 +45,7 @@ namespace Darkmatter.Features.GameplayFlow.Systems
|
|||||||
|
|
||||||
private IDisposable _assembledSub;
|
private IDisposable _assembledSub;
|
||||||
private IDisposable _colorAppliedSub;
|
private IDisposable _colorAppliedSub;
|
||||||
|
private IDisposable _drawingSelectedSub;
|
||||||
private CancellationTokenSource _autosaveCts;
|
private CancellationTokenSource _autosaveCts;
|
||||||
private CancellationTokenSource _scopeCts;
|
private CancellationTokenSource _scopeCts;
|
||||||
|
|
||||||
@@ -55,6 +58,7 @@ namespace Darkmatter.Features.GameplayFlow.Systems
|
|||||||
ICaptureFeature capture,
|
ICaptureFeature capture,
|
||||||
ILoadingScreen loadingScreen,
|
ILoadingScreen loadingScreen,
|
||||||
IGameplaySceneRefs refs,
|
IGameplaySceneRefs refs,
|
||||||
|
ISfxPlayer sfx,
|
||||||
IEventBus bus)
|
IEventBus bus)
|
||||||
{
|
{
|
||||||
_progression = progression;
|
_progression = progression;
|
||||||
@@ -65,6 +69,7 @@ namespace Darkmatter.Features.GameplayFlow.Systems
|
|||||||
_capture = capture;
|
_capture = capture;
|
||||||
_loadingScreen = loadingScreen;
|
_loadingScreen = loadingScreen;
|
||||||
_refs = refs;
|
_refs = refs;
|
||||||
|
_sfx = sfx;
|
||||||
_bus = bus;
|
_bus = bus;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,9 +94,7 @@ namespace Darkmatter.Features.GameplayFlow.Systems
|
|||||||
|
|
||||||
_assembledSub = _bus.Subscribe<ShapeAssembledSignal>(OnShapeAssembled);
|
_assembledSub = _bus.Subscribe<ShapeAssembledSignal>(OnShapeAssembled);
|
||||||
_colorAppliedSub = _bus.Subscribe<ColorAppliedSignal>(OnColorApplied);
|
_colorAppliedSub = _bus.Subscribe<ColorAppliedSignal>(OnColorApplied);
|
||||||
|
_drawingSelectedSub = _bus.Subscribe<DrawingSelectedSignal>(OnDrawingSelected);
|
||||||
Application.quitting += OnAppQuitting;
|
|
||||||
Application.focusChanged += OnAppFocusChanged;
|
|
||||||
|
|
||||||
_loadingScreen.SetProgress(1f);
|
_loadingScreen.SetProgress(1f);
|
||||||
if (_phase == DrawingPhase.Coloring)
|
if (_phase == DrawingPhase.Coloring)
|
||||||
@@ -127,6 +130,8 @@ namespace Darkmatter.Features.GameplayFlow.Systems
|
|||||||
{
|
{
|
||||||
await SaveCurrentAsync(ct);
|
await SaveCurrentAsync(ct);
|
||||||
_refs.Confetti.Play();
|
_refs.Confetti.Play();
|
||||||
|
_sfx.Play(SfxId.FireWorkLaunch);
|
||||||
|
_sfx.Play(SfxId.LevelComplete);
|
||||||
await _coloring.PlayCompletionAnimationAsync(ct);
|
await _coloring.PlayCompletionAnimationAsync(ct);
|
||||||
_progression.MarkCompleted(_templateId);
|
_progression.MarkCompleted(_templateId);
|
||||||
|
|
||||||
@@ -149,17 +154,6 @@ namespace Darkmatter.Features.GameplayFlow.Systems
|
|||||||
_bus.Publish(new DrawingSelectedSignal(nextId));
|
_bus.Publish(new DrawingSelectedSignal(nextId));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnApplicationPaused()
|
|
||||||
{
|
|
||||||
SaveCurrentAsync(CancellationToken.None).Forget();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnAppQuitting() => OnApplicationPaused();
|
|
||||||
|
|
||||||
private void OnAppFocusChanged(bool focused)
|
|
||||||
{
|
|
||||||
if (!focused) OnApplicationPaused();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnShapeAssembled(ShapeAssembledSignal signal)
|
private void OnShapeAssembled(ShapeAssembledSignal signal)
|
||||||
{
|
{
|
||||||
@@ -188,6 +182,24 @@ namespace Darkmatter.Features.GameplayFlow.Systems
|
|||||||
DebouncedAutosaveAsync(_autosaveCts.Token).Forget();
|
DebouncedAutosaveAsync(_autosaveCts.Token).Forget();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnDrawingSelected(DrawingSelectedSignal signal)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(signal.TemplateId)) return;
|
||||||
|
if (signal.TemplateId == _templateId) return;
|
||||||
|
EditAsync(signal.TemplateId).Forget();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async UniTaskVoid EditAsync(string newTemplateId)
|
||||||
|
{
|
||||||
|
await SaveCurrentAsync(CancellationToken.None);
|
||||||
|
_loadingScreen.Show();
|
||||||
|
_shapeBuilder.Clear();
|
||||||
|
_coloring.Clear();
|
||||||
|
await _scenes.LoadSceneAsync(nameof(GameScene.Colorbook), progress: null, cancellationToken: default);
|
||||||
|
await _scenes.UnloadSceneAsync(nameof(GameScene.Gameplay), progress: null, cancellationToken: default);
|
||||||
|
_bus.Publish(new DrawingSelectedSignal(newTemplateId));
|
||||||
|
}
|
||||||
|
|
||||||
private async UniTaskVoid DebouncedAutosaveAsync(CancellationToken ct)
|
private async UniTaskVoid DebouncedAutosaveAsync(CancellationToken ct)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -241,11 +253,10 @@ namespace Darkmatter.Features.GameplayFlow.Systems
|
|||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Application.quitting -= OnAppQuitting;
|
|
||||||
Application.focusChanged -= OnAppFocusChanged;
|
|
||||||
|
|
||||||
_assembledSub?.Dispose();
|
_assembledSub?.Dispose();
|
||||||
_colorAppliedSub?.Dispose();
|
_colorAppliedSub?.Dispose();
|
||||||
|
_drawingSelectedSub?.Dispose();
|
||||||
_autosaveCts?.Cancel();
|
_autosaveCts?.Cancel();
|
||||||
_autosaveCts?.Dispose();
|
_autosaveCts?.Dispose();
|
||||||
_scopeCts?.Cancel();
|
_scopeCts?.Cancel();
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using Darkmatter.Core.Contracts.Services.Audio;
|
||||||
using Darkmatter.Core.Data.Signals.Features.AppBoot;
|
using Darkmatter.Core.Data.Signals.Features.AppBoot;
|
||||||
using Darkmatter.Core.Data.Signals.Features.MainMenu;
|
using Darkmatter.Core.Data.Signals.Features.MainMenu;
|
||||||
|
using Darkmatter.Core.Enums.Services.Audio;
|
||||||
using Darkmatter.Libs.Observer;
|
using Darkmatter.Libs.Observer;
|
||||||
using UnityEngine;
|
|
||||||
using VContainer.Unity;
|
using VContainer.Unity;
|
||||||
|
|
||||||
namespace Darkmatter.Features.Mainmenu
|
namespace Darkmatter.Features.Mainmenu
|
||||||
@@ -11,17 +12,19 @@ namespace Darkmatter.Features.Mainmenu
|
|||||||
{
|
{
|
||||||
private readonly IEventBus _eventBus;
|
private readonly IEventBus _eventBus;
|
||||||
private readonly MainMenuView _view;
|
private readonly MainMenuView _view;
|
||||||
|
private readonly ISfxPlayer _sfxPlayer;
|
||||||
|
|
||||||
|
|
||||||
|
public MainMenuPresenter(MainMenuView view, IEventBus eventBus, ISfxPlayer sfxPlayer)
|
||||||
public MainMenuPresenter(MainMenuView view, IEventBus eventBus)
|
|
||||||
{
|
{
|
||||||
_view = view;
|
_view = view;
|
||||||
_eventBus = eventBus;
|
_eventBus = eventBus;
|
||||||
|
_sfxPlayer = sfxPlayer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Start()
|
public void Start()
|
||||||
{
|
{
|
||||||
|
_view.OnPlayBtnPressedEvent += OnPlayBtnPressed;
|
||||||
_view.OnPlayBtnClickedEvent += OnPlayBtnClicked;
|
_view.OnPlayBtnClickedEvent += OnPlayBtnClicked;
|
||||||
_eventBus.Subscribe<IntroCompletedSignal>(OnIntroComplete);
|
_eventBus.Subscribe<IntroCompletedSignal>(OnIntroComplete);
|
||||||
}
|
}
|
||||||
@@ -31,6 +34,11 @@ namespace Darkmatter.Features.Mainmenu
|
|||||||
_view.PlayMascotIntro();
|
_view.PlayMascotIntro();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnPlayBtnPressed()
|
||||||
|
{
|
||||||
|
_sfxPlayer.Play(SfxId.PlayButtonTap);
|
||||||
|
}
|
||||||
|
|
||||||
private void OnPlayBtnClicked()
|
private void OnPlayBtnClicked()
|
||||||
{
|
{
|
||||||
_eventBus.Publish(new PlayBtnClickedSignal());
|
_eventBus.Publish(new PlayBtnClickedSignal());
|
||||||
@@ -38,6 +46,7 @@ namespace Darkmatter.Features.Mainmenu
|
|||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
_view.OnPlayBtnPressedEvent -= OnPlayBtnPressed;
|
||||||
_view.OnPlayBtnClickedEvent -= OnPlayBtnClicked;
|
_view.OnPlayBtnClickedEvent -= OnPlayBtnClicked;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ namespace Darkmatter.Features.Mainmenu
|
|||||||
public class MainMenuView : MonoBehaviour
|
public class MainMenuView : MonoBehaviour
|
||||||
{
|
{
|
||||||
[Header("UI Elements")]
|
[Header("UI Elements")]
|
||||||
//[SerializeField] private Button playBtn;
|
|
||||||
[SerializeField] private PlayButtonView playBtn;
|
[SerializeField] private PlayButtonView playBtn;
|
||||||
[SerializeField] private SkeletonGraphic mascotSkeletonGraphic;
|
[SerializeField] private SkeletonGraphic mascotSkeletonGraphic;
|
||||||
|
|
||||||
@@ -20,12 +19,14 @@ namespace Darkmatter.Features.Mainmenu
|
|||||||
[SerializeField] private string helloAnimation = "hello";
|
[SerializeField] private string helloAnimation = "hello";
|
||||||
[SerializeField] private float helloInterval = 5f;
|
[SerializeField] private float helloInterval = 5f;
|
||||||
|
|
||||||
|
public event Action OnPlayBtnPressedEvent;
|
||||||
public event Action OnPlayBtnClickedEvent;
|
public event Action OnPlayBtnClickedEvent;
|
||||||
|
|
||||||
private CancellationTokenSource helloCts;
|
private CancellationTokenSource helloCts;
|
||||||
|
|
||||||
private void Start()
|
private void Start()
|
||||||
{
|
{
|
||||||
|
playBtn.OnPlayBtnPressedEvent += () => OnPlayBtnPressedEvent?.Invoke();
|
||||||
playBtn.OnPlayBtnClickedEvent += () => OnPlayBtnClickedEvent?.Invoke();
|
playBtn.OnPlayBtnClickedEvent += () => OnPlayBtnClickedEvent?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ namespace Darkmatter.Features.Mainmenu
|
|||||||
private Animator playBtnAnimator;
|
private Animator playBtnAnimator;
|
||||||
[SerializeField] private ParticleSystem playBtnParticle;
|
[SerializeField] private ParticleSystem playBtnParticle;
|
||||||
|
|
||||||
|
public event Action OnPlayBtnPressedEvent;
|
||||||
public event Action OnPlayBtnClickedEvent;
|
public event Action OnPlayBtnClickedEvent;
|
||||||
|
|
||||||
private void Awake()
|
private void Awake()
|
||||||
@@ -29,6 +30,7 @@ namespace Darkmatter.Features.Mainmenu
|
|||||||
{
|
{
|
||||||
playBtn.interactable = false;
|
playBtn.interactable = false;
|
||||||
playBtnAnimator.enabled = false;
|
playBtnAnimator.enabled = false;
|
||||||
|
OnPlayBtnPressedEvent?.Invoke();
|
||||||
PlayBtnSequenceAsync(this.GetCancellationTokenOnDestroy()).Forget();
|
PlayBtnSequenceAsync(this.GetCancellationTokenOnDestroy()).Forget();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using Darkmatter.Core;
|
||||||
using Darkmatter.Core.Data.Signals.Features.ShapeBuilder;
|
using Darkmatter.Core.Data.Signals.Features.ShapeBuilder;
|
||||||
using Darkmatter.Libs.Observer;
|
using Darkmatter.Libs.Observer;
|
||||||
using VContainer.Unity;
|
using VContainer.Unity;
|
||||||
@@ -24,12 +25,19 @@ namespace Darkmatter.Features.ShapeBuilder.UI
|
|||||||
_view.HideInstant();
|
_view.HideInstant();
|
||||||
_startedSub = _bus.Subscribe<ShapeBuilderStartedSignal>(_ => _view.Show());
|
_startedSub = _bus.Subscribe<ShapeBuilderStartedSignal>(_ => _view.Show());
|
||||||
_assembledSub = _bus.Subscribe<ShapeAssembledSignal>(_ => _view.Hide());
|
_assembledSub = _bus.Subscribe<ShapeAssembledSignal>(_ => _view.Hide());
|
||||||
|
_view.OnArtbookClicked += HandleArtbookClicked;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleArtbookClicked()
|
||||||
|
{
|
||||||
|
_bus.Publish(new OpenArtBookSignal());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_startedSub?.Dispose();
|
_startedSub?.Dispose();
|
||||||
_assembledSub?.Dispose();
|
_assembledSub?.Dispose();
|
||||||
|
_view.OnArtbookClicked -= HandleArtbookClicked;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
|
using System;
|
||||||
|
using Darkmatter.Features.ShapeBuilder.Systems;
|
||||||
using PrimeTween;
|
using PrimeTween;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
|
||||||
namespace Darkmatter.Features.ShapeBuilder.UI
|
namespace Darkmatter.Features.ShapeBuilder.UI
|
||||||
{
|
{
|
||||||
@@ -8,6 +11,7 @@ namespace Darkmatter.Features.ShapeBuilder.UI
|
|||||||
{
|
{
|
||||||
[SerializeField] private RectTransform spawnRoot;
|
[SerializeField] private RectTransform spawnRoot;
|
||||||
[SerializeField] private RectTransform animatedRoot;
|
[SerializeField] private RectTransform animatedRoot;
|
||||||
|
[SerializeField] private Button artbookButton;
|
||||||
[SerializeField] private float showDuration = 0.35f;
|
[SerializeField] private float showDuration = 0.35f;
|
||||||
[SerializeField] private float hideDuration = 0.25f;
|
[SerializeField] private float hideDuration = 0.25f;
|
||||||
[SerializeField] private Vector2 hiddenOffset = new(1500f, 0f);
|
[SerializeField] private Vector2 hiddenOffset = new(1500f, 0f);
|
||||||
@@ -15,6 +19,7 @@ namespace Darkmatter.Features.ShapeBuilder.UI
|
|||||||
private CanvasGroup _canvasGroup;
|
private CanvasGroup _canvasGroup;
|
||||||
private Vector2 _shownAnchoredPos;
|
private Vector2 _shownAnchoredPos;
|
||||||
private Sequence _activeSequence;
|
private Sequence _activeSequence;
|
||||||
|
public event Action OnArtbookClicked;
|
||||||
|
|
||||||
public RectTransform SpawnRoot => spawnRoot;
|
public RectTransform SpawnRoot => spawnRoot;
|
||||||
public float SpawnWidth => spawnRoot.rect.width;
|
public float SpawnWidth => spawnRoot.rect.width;
|
||||||
@@ -24,6 +29,12 @@ namespace Darkmatter.Features.ShapeBuilder.UI
|
|||||||
_canvasGroup = GetComponent<CanvasGroup>();
|
_canvasGroup = GetComponent<CanvasGroup>();
|
||||||
if (animatedRoot == null) animatedRoot = (RectTransform)transform;
|
if (animatedRoot == null) animatedRoot = (RectTransform)transform;
|
||||||
_shownAnchoredPos = animatedRoot.anchoredPosition;
|
_shownAnchoredPos = animatedRoot.anchoredPosition;
|
||||||
|
artbookButton.onClick.AddListener(HandleArtbookClicked);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleArtbookClicked()
|
||||||
|
{
|
||||||
|
OnArtbookClicked?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Sequence Show()
|
public Sequence Show()
|
||||||
@@ -73,5 +84,10 @@ namespace Darkmatter.Features.ShapeBuilder.UI
|
|||||||
{
|
{
|
||||||
KillActive();
|
KillActive();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnDestroy()
|
||||||
|
{
|
||||||
|
artbookButton.onClick.RemoveListener(HandleArtbookClicked);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
8
Assets/Darkmatter/Code/Services/Ads.meta
Normal file
8
Assets/Darkmatter/Code/Services/Ads.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 3a320ad839a14f578bb9ff149807ed84
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
8
Assets/Darkmatter/Code/Services/Ads/Installers.meta
Normal file
8
Assets/Darkmatter/Code/Services/Ads/Installers.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: fd05451b7a0c4e04ae9521e8052ee1e2
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
using Darkmatter.Core.Contracts.Services.Ads;
|
||||||
|
using Darkmatter.Core.Data.Static.Services.Ads;
|
||||||
|
using Darkmatter.Libs.Installers;
|
||||||
|
using UnityEngine;
|
||||||
|
using VContainer;
|
||||||
|
using VContainer.Unity;
|
||||||
|
|
||||||
|
namespace Darkmatter.Services.Ads
|
||||||
|
{
|
||||||
|
public class AdServiceModule : MonoBehaviour, IModule
|
||||||
|
{
|
||||||
|
[SerializeField] private AdUnitCatalogSO adUnitCatalog;
|
||||||
|
[SerializeField] private AdMobAdService adService;
|
||||||
|
|
||||||
|
public void Register(IContainerBuilder builder)
|
||||||
|
{
|
||||||
|
if (adUnitCatalog != null)
|
||||||
|
builder.RegisterComponent(adUnitCatalog);
|
||||||
|
|
||||||
|
var resolved = adService;
|
||||||
|
if (resolved == null) resolved = GetComponent<AdMobAdService>();
|
||||||
|
if (resolved == null) resolved = GetComponentInChildren<AdMobAdService>(includeInactive: true);
|
||||||
|
if (resolved == null) resolved = FindFirstObjectByType<AdMobAdService>(FindObjectsInactive.Include);
|
||||||
|
|
||||||
|
if (resolved != null)
|
||||||
|
builder.RegisterComponent<IAdService>(resolved);
|
||||||
|
else
|
||||||
|
Debug.LogError("[AdServiceModule] No AdMobAdService component found. Assign 'adService' field or add AdMobAdService MonoBehaviour to scene.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 3a17cb41935543a6b4f67be8f398d774
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
25
Assets/Darkmatter/Code/Services/Ads/Services.Ads.asmdef
Normal file
25
Assets/Darkmatter/Code/Services/Ads/Services.Ads.asmdef
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"name": "Services.Ads",
|
||||||
|
"rootNamespace": "Darkmatter.Services.Ads",
|
||||||
|
"references": [
|
||||||
|
"GUID:6a0a834eb41764f12ba55c3fb04a40cb",
|
||||||
|
"GUID:f51ebe6a0ceec4240a699833d6309b23",
|
||||||
|
"GUID:b0214a6008ed146ff8f122a6a9c2f6cc",
|
||||||
|
"GUID:c1c03c0e5b2f4412b9f2be1c20d6a9b1"
|
||||||
|
],
|
||||||
|
"includePlatforms": [],
|
||||||
|
"excludePlatforms": [],
|
||||||
|
"allowUnsafeCode": false,
|
||||||
|
"overrideReferences": false,
|
||||||
|
"precompiledReferences": [],
|
||||||
|
"autoReferenced": true,
|
||||||
|
"defineConstraints": [],
|
||||||
|
"versionDefines": [
|
||||||
|
{
|
||||||
|
"name": "com.google.ads.mobile",
|
||||||
|
"expression": "1.0.0",
|
||||||
|
"define": "GOOGLE_MOBILE_ADS"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"noEngineReferences": false
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 662a8e56a1724c84ad8c10211211f0d5
|
||||||
|
AssemblyDefinitionImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
8
Assets/Darkmatter/Code/Services/Ads/Systems.meta
Normal file
8
Assets/Darkmatter/Code/Services/Ads/Systems.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 6fbb9ad075cb419095482b3d74c4311c
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
535
Assets/Darkmatter/Code/Services/Ads/Systems/AdMobAdService.cs
Normal file
535
Assets/Darkmatter/Code/Services/Ads/Systems/AdMobAdService.cs
Normal file
@@ -0,0 +1,535 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
|
using Cysharp.Threading.Tasks;
|
||||||
|
using Darkmatter.Core.Contracts.Services.Ads;
|
||||||
|
using Darkmatter.Core.Data.Dynamic.Services.Ads;
|
||||||
|
using Darkmatter.Core.Data.Static.Services.Ads;
|
||||||
|
using Darkmatter.Core.Enums.Services.Ads;
|
||||||
|
using UnityEngine;
|
||||||
|
using AdFormat = Darkmatter.Core.Enums.Services.Ads.AdFormat;
|
||||||
|
#if GOOGLE_MOBILE_ADS
|
||||||
|
using GoogleMobileAds.Api;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace Darkmatter.Services.Ads
|
||||||
|
{
|
||||||
|
public class AdMobAdService : MonoBehaviour, IAdService
|
||||||
|
{
|
||||||
|
[SerializeField] private AdUnitCatalogSO catalog;
|
||||||
|
[Tooltip("Auto-reload after dismiss/failure for non-banner formats.")]
|
||||||
|
[SerializeField] private bool autoReload = true;
|
||||||
|
[Tooltip("Seconds between auto-reload retries on failure.")]
|
||||||
|
[SerializeField, Min(1f)] private float reloadDelaySeconds = 5f;
|
||||||
|
|
||||||
|
public bool IsInitialized => _initialized;
|
||||||
|
public event Action<AdFormat, AdLoadState> LoadStateChanged;
|
||||||
|
|
||||||
|
private bool _initialized;
|
||||||
|
private bool _hasUserConsent = true;
|
||||||
|
private bool _isChildDirected;
|
||||||
|
private CancellationTokenSource _lifetimeCts;
|
||||||
|
|
||||||
|
private readonly Dictionary<AdFormat, AdLoadState> _states = new();
|
||||||
|
|
||||||
|
#if GOOGLE_MOBILE_ADS
|
||||||
|
private InterstitialAd _interstitial;
|
||||||
|
private RewardedAd _rewarded;
|
||||||
|
private RewardedInterstitialAd _rewardedInterstitial;
|
||||||
|
private AppOpenAd _appOpen;
|
||||||
|
private BannerView _banner;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
_lifetimeCts = new CancellationTokenSource();
|
||||||
|
foreach (AdFormat fmt in Enum.GetValues(typeof(AdFormat)))
|
||||||
|
_states[fmt] = AdLoadState.Idle;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDestroy()
|
||||||
|
{
|
||||||
|
_lifetimeCts?.Cancel();
|
||||||
|
_lifetimeCts?.Dispose();
|
||||||
|
_lifetimeCts = null;
|
||||||
|
|
||||||
|
#if GOOGLE_MOBILE_ADS
|
||||||
|
_interstitial?.Destroy();
|
||||||
|
_rewarded?.Destroy();
|
||||||
|
_rewardedInterstitial?.Destroy();
|
||||||
|
_appOpen?.Destroy();
|
||||||
|
_banner?.Destroy();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
public async UniTask InitializeAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (_initialized) return;
|
||||||
|
if (catalog == null)
|
||||||
|
{
|
||||||
|
Debug.LogError("[AdMobAdService] No AdUnitCatalogSO assigned.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if GOOGLE_MOBILE_ADS
|
||||||
|
ApplyRequestConfiguration();
|
||||||
|
|
||||||
|
var tcs = new UniTaskCompletionSource<bool>();
|
||||||
|
MobileAds.Initialize(_ => tcs.TrySetResult(true));
|
||||||
|
|
||||||
|
using (cancellationToken.Register(() => tcs.TrySetCanceled(cancellationToken)))
|
||||||
|
{
|
||||||
|
await tcs.Task;
|
||||||
|
}
|
||||||
|
|
||||||
|
_initialized = true;
|
||||||
|
Debug.Log("[AdMobAdService] Initialized.");
|
||||||
|
#else
|
||||||
|
await UniTask.CompletedTask;
|
||||||
|
_initialized = true;
|
||||||
|
Debug.LogWarning("[AdMobAdService] GOOGLE_MOBILE_ADS not defined. Service will no-op. Add scripting define after importing Google Mobile Ads SDK.");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
public async UniTask<bool> LoadAsync(AdFormat format, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (!_initialized)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("[AdMobAdService] Load called before init.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if GOOGLE_MOBILE_ADS
|
||||||
|
string unitId = catalog.GetUnitId(format, Application.platform);
|
||||||
|
if (string.IsNullOrEmpty(unitId))
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[AdMobAdService] No unit ID for {format} on {Application.platform}.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetState(format, AdLoadState.Loading);
|
||||||
|
var request = new AdRequest();
|
||||||
|
|
||||||
|
switch (format)
|
||||||
|
{
|
||||||
|
case AdFormat.Interstitial:
|
||||||
|
return await LoadInterstitialAsync(unitId, request, cancellationToken);
|
||||||
|
case AdFormat.Rewarded:
|
||||||
|
return await LoadRewardedAsync(unitId, request, cancellationToken);
|
||||||
|
case AdFormat.RewardedInterstitial:
|
||||||
|
return await LoadRewardedInterstitialAsync(unitId, request, cancellationToken);
|
||||||
|
case AdFormat.AppOpen:
|
||||||
|
return await LoadAppOpenAsync(unitId, request, cancellationToken);
|
||||||
|
case AdFormat.Banner:
|
||||||
|
Debug.LogWarning("[AdMobAdService] Use ShowBannerAsync for banners.");
|
||||||
|
SetState(format, AdLoadState.Failed);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
#else
|
||||||
|
await UniTask.CompletedTask;
|
||||||
|
SetState(format, AdLoadState.Failed);
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsReady(AdFormat format)
|
||||||
|
{
|
||||||
|
if (!_initialized) return false;
|
||||||
|
#if GOOGLE_MOBILE_ADS
|
||||||
|
return format switch
|
||||||
|
{
|
||||||
|
AdFormat.Interstitial => _interstitial != null && _interstitial.CanShowAd(),
|
||||||
|
AdFormat.Rewarded => _rewarded != null && _rewarded.CanShowAd(),
|
||||||
|
AdFormat.RewardedInterstitial => _rewardedInterstitial != null && _rewardedInterstitial.CanShowAd(),
|
||||||
|
AdFormat.AppOpen => _appOpen != null && _appOpen.CanShowAd(),
|
||||||
|
_ => false
|
||||||
|
};
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
public async UniTask<AdShowResult> ShowAsync(AdFormat format, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (!_initialized) return AdShowResult.Failure("Not initialized.");
|
||||||
|
|
||||||
|
#if GOOGLE_MOBILE_ADS
|
||||||
|
if (!IsReady(format))
|
||||||
|
{
|
||||||
|
bool loaded = await LoadAsync(format, cancellationToken);
|
||||||
|
if (!loaded) return AdShowResult.Failure($"{format} failed to load.");
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (format)
|
||||||
|
{
|
||||||
|
case AdFormat.Interstitial: return await ShowInterstitialAsync(cancellationToken);
|
||||||
|
case AdFormat.Rewarded: return await ShowRewardedAsync(cancellationToken);
|
||||||
|
case AdFormat.RewardedInterstitial: return await ShowRewardedInterstitialAsync(cancellationToken);
|
||||||
|
case AdFormat.AppOpen: return await ShowAppOpenAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
return AdShowResult.Failure($"{format} not supported by ShowAsync.");
|
||||||
|
#else
|
||||||
|
await UniTask.CompletedTask;
|
||||||
|
return AdShowResult.Failure("GOOGLE_MOBILE_ADS not defined.");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
public async UniTask<bool> ShowBannerAsync(BannerSize size, BannerPosition position, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (!_initialized) return false;
|
||||||
|
|
||||||
|
#if GOOGLE_MOBILE_ADS
|
||||||
|
string unitId = catalog.GetUnitId(AdFormat.Banner, Application.platform);
|
||||||
|
if (string.IsNullOrEmpty(unitId)) return false;
|
||||||
|
|
||||||
|
_banner?.Destroy();
|
||||||
|
_banner = new BannerView(unitId, MapBannerSize(size), MapBannerPosition(position));
|
||||||
|
|
||||||
|
var tcs = new UniTaskCompletionSource<bool>();
|
||||||
|
_banner.OnBannerAdLoaded += () => tcs.TrySetResult(true);
|
||||||
|
_banner.OnBannerAdLoadFailed += err =>
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[AdMobAdService] Banner load failed: {err}");
|
||||||
|
tcs.TrySetResult(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
SetState(AdFormat.Banner, AdLoadState.Loading);
|
||||||
|
_banner.LoadAd(new AdRequest());
|
||||||
|
|
||||||
|
using (cancellationToken.Register(() => tcs.TrySetCanceled(cancellationToken)))
|
||||||
|
{
|
||||||
|
bool ok = await tcs.Task;
|
||||||
|
SetState(AdFormat.Banner, ok ? AdLoadState.Loaded : AdLoadState.Failed);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
await UniTask.CompletedTask;
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
public void HideBanner()
|
||||||
|
{
|
||||||
|
#if GOOGLE_MOBILE_ADS
|
||||||
|
_banner?.Hide();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DestroyBanner()
|
||||||
|
{
|
||||||
|
#if GOOGLE_MOBILE_ADS
|
||||||
|
_banner?.Destroy();
|
||||||
|
_banner = null;
|
||||||
|
SetState(AdFormat.Banner, AdLoadState.Idle);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetConsent(bool hasUserConsent, bool isChildDirected)
|
||||||
|
{
|
||||||
|
_hasUserConsent = hasUserConsent;
|
||||||
|
_isChildDirected = isChildDirected;
|
||||||
|
#if GOOGLE_MOBILE_ADS
|
||||||
|
if (_initialized) ApplyRequestConfiguration();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#if GOOGLE_MOBILE_ADS
|
||||||
|
private void ApplyRequestConfiguration()
|
||||||
|
{
|
||||||
|
var config = new RequestConfiguration
|
||||||
|
{
|
||||||
|
TagForChildDirectedTreatment = _isChildDirected
|
||||||
|
? TagForChildDirectedTreatment.True
|
||||||
|
: TagForChildDirectedTreatment.Unspecified,
|
||||||
|
TagForUnderAgeOfConsent = _hasUserConsent
|
||||||
|
? TagForUnderAgeOfConsent.Unspecified
|
||||||
|
: TagForUnderAgeOfConsent.True
|
||||||
|
};
|
||||||
|
|
||||||
|
if (catalog.TestDeviceIds is { Count: > 0 })
|
||||||
|
{
|
||||||
|
config.TestDeviceIds = new List<string>(catalog.TestDeviceIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
MobileAds.SetRequestConfiguration(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async UniTask<bool> LoadInterstitialAsync(string unitId, AdRequest request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var tcs = new UniTaskCompletionSource<bool>();
|
||||||
|
InterstitialAd.Load(unitId, request, (ad, error) =>
|
||||||
|
{
|
||||||
|
if (error != null || ad == null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[AdMobAdService] Interstitial load failed: {error}");
|
||||||
|
tcs.TrySetResult(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_interstitial?.Destroy();
|
||||||
|
_interstitial = ad;
|
||||||
|
WireFullScreenEvents(ad, AdFormat.Interstitial);
|
||||||
|
tcs.TrySetResult(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
using (cancellationToken.Register(() => tcs.TrySetCanceled(cancellationToken)))
|
||||||
|
{
|
||||||
|
bool ok = await tcs.Task;
|
||||||
|
SetState(AdFormat.Interstitial, ok ? AdLoadState.Loaded : AdLoadState.Failed);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async UniTask<bool> LoadRewardedAsync(string unitId, AdRequest request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var tcs = new UniTaskCompletionSource<bool>();
|
||||||
|
RewardedAd.Load(unitId, request, (ad, error) =>
|
||||||
|
{
|
||||||
|
if (error != null || ad == null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[AdMobAdService] Rewarded load failed: {error}");
|
||||||
|
tcs.TrySetResult(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_rewarded?.Destroy();
|
||||||
|
_rewarded = ad;
|
||||||
|
WireFullScreenEvents(ad, AdFormat.Rewarded);
|
||||||
|
tcs.TrySetResult(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
using (cancellationToken.Register(() => tcs.TrySetCanceled(cancellationToken)))
|
||||||
|
{
|
||||||
|
bool ok = await tcs.Task;
|
||||||
|
SetState(AdFormat.Rewarded, ok ? AdLoadState.Loaded : AdLoadState.Failed);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async UniTask<bool> LoadRewardedInterstitialAsync(string unitId, AdRequest request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var tcs = new UniTaskCompletionSource<bool>();
|
||||||
|
RewardedInterstitialAd.Load(unitId, request, (ad, error) =>
|
||||||
|
{
|
||||||
|
if (error != null || ad == null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[AdMobAdService] RewardedInterstitial load failed: {error}");
|
||||||
|
tcs.TrySetResult(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_rewardedInterstitial?.Destroy();
|
||||||
|
_rewardedInterstitial = ad;
|
||||||
|
WireFullScreenEvents(ad, AdFormat.RewardedInterstitial);
|
||||||
|
tcs.TrySetResult(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
using (cancellationToken.Register(() => tcs.TrySetCanceled(cancellationToken)))
|
||||||
|
{
|
||||||
|
bool ok = await tcs.Task;
|
||||||
|
SetState(AdFormat.RewardedInterstitial, ok ? AdLoadState.Loaded : AdLoadState.Failed);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async UniTask<bool> LoadAppOpenAsync(string unitId, AdRequest request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var tcs = new UniTaskCompletionSource<bool>();
|
||||||
|
AppOpenAd.Load(unitId, request, (ad, error) =>
|
||||||
|
{
|
||||||
|
if (error != null || ad == null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[AdMobAdService] AppOpen load failed: {error}");
|
||||||
|
tcs.TrySetResult(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_appOpen?.Destroy();
|
||||||
|
_appOpen = ad;
|
||||||
|
WireFullScreenEvents(ad, AdFormat.AppOpen);
|
||||||
|
tcs.TrySetResult(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
using (cancellationToken.Register(() => tcs.TrySetCanceled(cancellationToken)))
|
||||||
|
{
|
||||||
|
bool ok = await tcs.Task;
|
||||||
|
SetState(AdFormat.AppOpen, ok ? AdLoadState.Loaded : AdLoadState.Failed);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async UniTask<AdShowResult> ShowInterstitialAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var tcs = new UniTaskCompletionSource<AdShowResult>();
|
||||||
|
Action onClosed = () => tcs.TrySetResult(AdShowResult.Success());
|
||||||
|
Action<AdError> onFailed = err => tcs.TrySetResult(AdShowResult.Failure(err?.GetMessage()));
|
||||||
|
|
||||||
|
_interstitial.OnAdFullScreenContentClosed += onClosed;
|
||||||
|
_interstitial.OnAdFullScreenContentFailed += onFailed;
|
||||||
|
_interstitial.Show();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (cancellationToken.Register(() => tcs.TrySetCanceled(cancellationToken)))
|
||||||
|
return await tcs.Task;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (_interstitial != null)
|
||||||
|
{
|
||||||
|
_interstitial.OnAdFullScreenContentClosed -= onClosed;
|
||||||
|
_interstitial.OnAdFullScreenContentFailed -= onFailed;
|
||||||
|
}
|
||||||
|
ScheduleReload(AdFormat.Interstitial);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async UniTask<AdShowResult> ShowRewardedAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var tcs = new UniTaskCompletionSource<AdShowResult>();
|
||||||
|
bool earned = false;
|
||||||
|
AdReward reward = default;
|
||||||
|
|
||||||
|
Action onClosed = () => tcs.TrySetResult(earned ? AdShowResult.WithReward(reward) : AdShowResult.Success());
|
||||||
|
Action<AdError> onFailed = err => tcs.TrySetResult(AdShowResult.Failure(err?.GetMessage()));
|
||||||
|
|
||||||
|
_rewarded.OnAdFullScreenContentClosed += onClosed;
|
||||||
|
_rewarded.OnAdFullScreenContentFailed += onFailed;
|
||||||
|
_rewarded.Show(r =>
|
||||||
|
{
|
||||||
|
earned = true;
|
||||||
|
reward = new AdReward(r.Type, r.Amount);
|
||||||
|
});
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (cancellationToken.Register(() => tcs.TrySetCanceled(cancellationToken)))
|
||||||
|
return await tcs.Task;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (_rewarded != null)
|
||||||
|
{
|
||||||
|
_rewarded.OnAdFullScreenContentClosed -= onClosed;
|
||||||
|
_rewarded.OnAdFullScreenContentFailed -= onFailed;
|
||||||
|
}
|
||||||
|
ScheduleReload(AdFormat.Rewarded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async UniTask<AdShowResult> ShowRewardedInterstitialAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var tcs = new UniTaskCompletionSource<AdShowResult>();
|
||||||
|
bool earned = false;
|
||||||
|
AdReward reward = default;
|
||||||
|
|
||||||
|
Action onClosed = () => tcs.TrySetResult(earned ? AdShowResult.WithReward(reward) : AdShowResult.Success());
|
||||||
|
Action<AdError> onFailed = err => tcs.TrySetResult(AdShowResult.Failure(err?.GetMessage()));
|
||||||
|
|
||||||
|
_rewardedInterstitial.OnAdFullScreenContentClosed += onClosed;
|
||||||
|
_rewardedInterstitial.OnAdFullScreenContentFailed += onFailed;
|
||||||
|
_rewardedInterstitial.Show(r =>
|
||||||
|
{
|
||||||
|
earned = true;
|
||||||
|
reward = new AdReward(r.Type, r.Amount);
|
||||||
|
});
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (cancellationToken.Register(() => tcs.TrySetCanceled(cancellationToken)))
|
||||||
|
return await tcs.Task;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (_rewardedInterstitial != null)
|
||||||
|
{
|
||||||
|
_rewardedInterstitial.OnAdFullScreenContentClosed -= onClosed;
|
||||||
|
_rewardedInterstitial.OnAdFullScreenContentFailed -= onFailed;
|
||||||
|
}
|
||||||
|
ScheduleReload(AdFormat.RewardedInterstitial);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async UniTask<AdShowResult> ShowAppOpenAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var tcs = new UniTaskCompletionSource<AdShowResult>();
|
||||||
|
Action onClosed = () => tcs.TrySetResult(AdShowResult.Success());
|
||||||
|
Action<AdError> onFailed = err => tcs.TrySetResult(AdShowResult.Failure(err?.GetMessage()));
|
||||||
|
|
||||||
|
_appOpen.OnAdFullScreenContentClosed += onClosed;
|
||||||
|
_appOpen.OnAdFullScreenContentFailed += onFailed;
|
||||||
|
_appOpen.Show();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (cancellationToken.Register(() => tcs.TrySetCanceled(cancellationToken)))
|
||||||
|
return await tcs.Task;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (_appOpen != null)
|
||||||
|
{
|
||||||
|
_appOpen.OnAdFullScreenContentClosed -= onClosed;
|
||||||
|
_appOpen.OnAdFullScreenContentFailed -= onFailed;
|
||||||
|
}
|
||||||
|
ScheduleReload(AdFormat.AppOpen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WireFullScreenEvents(InterstitialAd ad, AdFormat format) =>
|
||||||
|
ad.OnAdFullScreenContentClosed += () => SetState(format, AdLoadState.Idle);
|
||||||
|
|
||||||
|
private void WireFullScreenEvents(RewardedAd ad, AdFormat format) =>
|
||||||
|
ad.OnAdFullScreenContentClosed += () => SetState(format, AdLoadState.Idle);
|
||||||
|
|
||||||
|
private void WireFullScreenEvents(RewardedInterstitialAd ad, AdFormat format) =>
|
||||||
|
ad.OnAdFullScreenContentClosed += () => SetState(format, AdLoadState.Idle);
|
||||||
|
|
||||||
|
private void WireFullScreenEvents(AppOpenAd ad, AdFormat format) =>
|
||||||
|
ad.OnAdFullScreenContentClosed += () => SetState(format, AdLoadState.Idle);
|
||||||
|
|
||||||
|
private void ScheduleReload(AdFormat format)
|
||||||
|
{
|
||||||
|
if (!autoReload || _lifetimeCts == null) return;
|
||||||
|
ReloadAfterDelayAsync(format, _lifetimeCts.Token).Forget();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async UniTaskVoid ReloadAfterDelayAsync(AdFormat format, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await UniTask.Delay(TimeSpan.FromSeconds(reloadDelaySeconds), cancellationToken: cancellationToken);
|
||||||
|
await LoadAsync(format, cancellationToken);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AdSize MapBannerSize(BannerSize size) => size switch
|
||||||
|
{
|
||||||
|
BannerSize.Banner => AdSize.Banner,
|
||||||
|
BannerSize.LargeBanner => AdSize.LargeBanner,
|
||||||
|
BannerSize.MediumRectangle => AdSize.MediumRectangle,
|
||||||
|
BannerSize.FullBanner => AdSize.IABBanner,
|
||||||
|
BannerSize.Leaderboard => AdSize.Leaderboard,
|
||||||
|
BannerSize.SmartBanner => AdSize.SmartBanner,
|
||||||
|
BannerSize.AnchoredAdaptive => AdSize.GetCurrentOrientationAnchoredAdaptiveBannerAdSizeWithWidth(AdSize.FullWidth),
|
||||||
|
_ => AdSize.Banner
|
||||||
|
};
|
||||||
|
|
||||||
|
private static AdPosition MapBannerPosition(BannerPosition position) => position switch
|
||||||
|
{
|
||||||
|
BannerPosition.Top => AdPosition.Top,
|
||||||
|
BannerPosition.Bottom => AdPosition.Bottom,
|
||||||
|
BannerPosition.TopLeft => AdPosition.TopLeft,
|
||||||
|
BannerPosition.TopRight => AdPosition.TopRight,
|
||||||
|
BannerPosition.BottomLeft => AdPosition.BottomLeft,
|
||||||
|
BannerPosition.BottomRight => AdPosition.BottomRight,
|
||||||
|
BannerPosition.Center => AdPosition.Center,
|
||||||
|
_ => AdPosition.Bottom
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
private void SetState(AdFormat format, AdLoadState state)
|
||||||
|
{
|
||||||
|
_states[format] = state;
|
||||||
|
LoadStateChanged?.Invoke(format, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 018ba83ad51c4d9a89a904ba3d21ead2
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -10,12 +10,24 @@ namespace Darkmatter.Services.Audio
|
|||||||
public class AudioServiceModule : MonoBehaviour, IModule
|
public class AudioServiceModule : MonoBehaviour, IModule
|
||||||
{
|
{
|
||||||
[SerializeField] private SfxCatalogSO sfxCatalog;
|
[SerializeField] private SfxCatalogSO sfxCatalog;
|
||||||
|
[SerializeField] private AudioService audioService;
|
||||||
|
|
||||||
public void Register(IContainerBuilder builder)
|
public void Register(IContainerBuilder builder)
|
||||||
{
|
{
|
||||||
if (sfxCatalog != null)
|
if (sfxCatalog == null)
|
||||||
builder.RegisterComponent(sfxCatalog);
|
{
|
||||||
builder.Register<IAudioService, AudioService>(Lifetime.Singleton);
|
Debug.LogError("[AudioServiceModule] SfxCatalogSO not assigned.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
builder.RegisterComponent(sfxCatalog);
|
||||||
|
|
||||||
|
if (audioService == null)
|
||||||
|
{
|
||||||
|
Debug.LogError("[AudioServiceModule] AudioService not assigned. Assign the scene component to the 'audioService' field.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
builder.RegisterComponent<IAudioService>(audioService);
|
||||||
|
|
||||||
builder.Register<ISfxPlayer, SfxPlayer>(Lifetime.Singleton);
|
builder.Register<ISfxPlayer, SfxPlayer>(Lifetime.Singleton);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,8 +72,18 @@ namespace Darkmatter.Services.Audio
|
|||||||
private Dictionary<AudioChannel, AudioMixerGroup> _mixerGroups;
|
private Dictionary<AudioChannel, AudioMixerGroup> _mixerGroups;
|
||||||
private float[] _currentWeights;
|
private float[] _currentWeights;
|
||||||
|
|
||||||
|
private bool _initialized;
|
||||||
|
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
InitializeAsync(default).Forget();
|
||||||
|
}
|
||||||
|
|
||||||
public UniTask InitializeAsync(CancellationToken cancellationToken)
|
public UniTask InitializeAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
if (_initialized) return UniTask.CompletedTask;
|
||||||
|
_initialized = true;
|
||||||
|
|
||||||
if (sourceRoot == null) sourceRoot = transform;
|
if (sourceRoot == null) sourceRoot = transform;
|
||||||
_lifetimeCts = new CancellationTokenSource();
|
_lifetimeCts = new CancellationTokenSource();
|
||||||
|
|
||||||
@@ -336,8 +346,11 @@ namespace Darkmatter.Services.Audio
|
|||||||
source.loop = request.PlayMode == AudioPlayMode.Loop;
|
source.loop = request.PlayMode == AudioPlayMode.Loop;
|
||||||
|
|
||||||
source.transform.localPosition = Vector3.zero;
|
source.transform.localPosition = Vector3.zero;
|
||||||
|
source.enabled = true;
|
||||||
|
if (!source.gameObject.activeInHierarchy) source.gameObject.SetActive(true);
|
||||||
|
|
||||||
source.Play();
|
source.Play();
|
||||||
|
Debug.Log($"[AudioService] Play clip='{(request.Clip != null ? request.Clip.name : "null")}' ch={request.Channel} vol={source.volume:F2} mixer={(source.outputAudioMixerGroup != null ? source.outputAudioMixerGroup.name : "none")} listener={(AudioListener.volume)} muted={AudioListener.pause} isPlaying={source.isPlaying}");
|
||||||
if (IsChannelPaused(request.Channel))
|
if (IsChannelPaused(request.Channel))
|
||||||
{
|
{
|
||||||
source.Pause();
|
source.Pause();
|
||||||
|
|||||||
@@ -1,73 +1,115 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Cysharp.Threading.Tasks;
|
using Cysharp.Threading.Tasks;
|
||||||
|
using Darkmatter.Core.Contracts.Services.Camera;
|
||||||
using Darkmatter.Core.Contracts.Services.Capture;
|
using Darkmatter.Core.Contracts.Services.Capture;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using CameraType = Darkmatter.Core.Enums.Services.Camera.CameraType;
|
||||||
|
|
||||||
namespace Darkmatter.Services.Capture
|
namespace Darkmatter.Services.Capture
|
||||||
{
|
{
|
||||||
public class CaptureService : ICaptureService
|
public class CaptureService : ICaptureService
|
||||||
{
|
{
|
||||||
|
private readonly ICameraService _cameraService;
|
||||||
|
|
||||||
|
public CaptureService(ICameraService cameraService) => _cameraService = cameraService;
|
||||||
|
|
||||||
public async UniTask<byte[]> CapturePngAsync(GameObject captureObject, float scale,
|
public async UniTask<byte[]> CapturePngAsync(GameObject captureObject, float scale,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
|
if (captureObject == null) return null;
|
||||||
|
var paperRT = captureObject.transform as RectTransform;
|
||||||
var paperCanvas = GetRootCanvas(captureObject);
|
var paperCanvas = GetRootCanvas(captureObject);
|
||||||
|
var cam = _cameraService.GetCamera(CameraType.CaptureCamera);
|
||||||
|
if (paperCanvas == null || paperRT == null || cam == null) return null;
|
||||||
|
|
||||||
|
int sw = Screen.width;
|
||||||
|
int sh = Screen.height;
|
||||||
|
|
||||||
var disabledCanvases = DisableOtherRootCanvases(paperCanvas);
|
var disabledCanvases = DisableOtherRootCanvases(paperCanvas);
|
||||||
var cam = Camera.main;
|
var disabledGraphics = HideNonPaperGraphics(paperRT);
|
||||||
CameraClearFlags prevFlags = default;
|
|
||||||
Color prevBg = default;
|
|
||||||
if (cam != null)
|
|
||||||
{
|
|
||||||
prevFlags = cam.clearFlags;
|
|
||||||
prevBg = cam.backgroundColor;
|
|
||||||
cam.clearFlags = CameraClearFlags.SolidColor;
|
|
||||||
cam.backgroundColor = new Color(0f, 0f, 0f, 0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
await UniTask.WaitForEndOfFrame(cancellationToken);
|
Rect crop = ComputeCropRect(captureObject, sw, sh);
|
||||||
|
int cropW = Mathf.Max(1, (int)crop.width);
|
||||||
|
int cropH = Mathf.Max(1, (int)crop.height);
|
||||||
|
|
||||||
|
var prevFlags = cam.clearFlags;
|
||||||
|
var prevBg = cam.backgroundColor;
|
||||||
|
var prevTarget = cam.targetTexture;
|
||||||
|
cam.clearFlags = CameraClearFlags.SolidColor;
|
||||||
|
cam.backgroundColor = new Color(0f, 0f, 0f, 0f);
|
||||||
|
|
||||||
|
var rt = RenderTexture.GetTemporary(sw, sh, 24, RenderTextureFormat.ARGB32);
|
||||||
|
cam.targetTexture = rt;
|
||||||
|
|
||||||
|
var prevMode = paperCanvas.renderMode;
|
||||||
|
var prevWorldCam = paperCanvas.worldCamera;
|
||||||
|
var prevPlaneDist = paperCanvas.planeDistance;
|
||||||
|
paperCanvas.renderMode = RenderMode.ScreenSpaceCamera;
|
||||||
|
paperCanvas.worldCamera = cam;
|
||||||
|
paperCanvas.planeDistance = Mathf.Max(0.5f, (cam.nearClipPlane + cam.farClipPlane) * 0.5f);
|
||||||
|
|
||||||
var fullScreen = ScreenCapture.CaptureScreenshotAsTexture();
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Rect crop = ComputeCropRect(captureObject, fullScreen.width, fullScreen.height);
|
await UniTask.WaitForEndOfFrame(cancellationToken);
|
||||||
int cropW = Mathf.Max(1, (int)crop.width);
|
Canvas.ForceUpdateCanvases();
|
||||||
int cropH = Mathf.Max(1, (int)crop.height);
|
cam.Render();
|
||||||
|
|
||||||
|
var prevActive = RenderTexture.active;
|
||||||
|
RenderTexture.active = rt;
|
||||||
|
var fullScreen = new Texture2D(sw, sh, TextureFormat.RGBA32, mipChain: false);
|
||||||
|
fullScreen.ReadPixels(new Rect(0, 0, sw, sh), 0, 0);
|
||||||
|
fullScreen.Apply();
|
||||||
|
RenderTexture.active = prevActive;
|
||||||
|
|
||||||
var cropped = new Texture2D(cropW, cropH, TextureFormat.RGBA32, mipChain: false);
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
cropped.SetPixels(fullScreen.GetPixels((int)crop.x, (int)crop.y, cropW, cropH));
|
var cropped = new Texture2D(cropW, cropH, TextureFormat.RGBA32, mipChain: false);
|
||||||
cropped.Apply();
|
|
||||||
|
|
||||||
int targetW = Mathf.Max(1, Mathf.RoundToInt(cropW * scale));
|
|
||||||
int targetH = Mathf.Max(1, Mathf.RoundToInt(cropH * scale));
|
|
||||||
Texture2D output = cropped;
|
|
||||||
if (output.width != targetW || output.height != targetH)
|
|
||||||
output = Resize(cropped, targetW, targetH);
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return output.EncodeToPNG();
|
cropped.SetPixels(fullScreen.GetPixels((int)crop.x, (int)crop.y, cropW, cropH));
|
||||||
|
cropped.Apply();
|
||||||
|
|
||||||
|
int targetW = Mathf.Max(1, Mathf.RoundToInt(cropW * scale));
|
||||||
|
int targetH = Mathf.Max(1, Mathf.RoundToInt(cropH * scale));
|
||||||
|
Texture2D output = cropped;
|
||||||
|
if (output.width != targetW || output.height != targetH)
|
||||||
|
output = Resize(cropped, targetW, targetH);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return output.EncodeToPNG();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (output != cropped) Object.Destroy(output);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (output != cropped) Object.Destroy(output);
|
Object.Destroy(cropped);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
Object.Destroy(cropped);
|
Object.Destroy(fullScreen);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
Object.Destroy(fullScreen);
|
paperCanvas.renderMode = prevMode;
|
||||||
foreach (var c in disabledCanvases) if (c != null) c.enabled = true;
|
paperCanvas.worldCamera = prevWorldCam;
|
||||||
if (cam != null)
|
paperCanvas.planeDistance = prevPlaneDist;
|
||||||
{
|
cam.targetTexture = prevTarget;
|
||||||
cam.clearFlags = prevFlags;
|
cam.clearFlags = prevFlags;
|
||||||
cam.backgroundColor = prevBg;
|
cam.backgroundColor = prevBg;
|
||||||
}
|
RenderTexture.ReleaseTemporary(rt);
|
||||||
|
foreach (var g in disabledGraphics)
|
||||||
|
if (g != null)
|
||||||
|
g.enabled = true;
|
||||||
|
foreach (var c in disabledCanvases)
|
||||||
|
if (c != null)
|
||||||
|
c.enabled = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,6 +130,23 @@ namespace Darkmatter.Services.Capture
|
|||||||
c.enabled = false;
|
c.enabled = false;
|
||||||
disabled.Add(c);
|
disabled.Add(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return disabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<UnityEngine.UI.Graphic> HideNonPaperGraphics(Transform paper)
|
||||||
|
{
|
||||||
|
var disabled = new List<UnityEngine.UI.Graphic>();
|
||||||
|
if (paper == null) return disabled;
|
||||||
|
var all = Object.FindObjectsByType<UnityEngine.UI.Graphic>(FindObjectsSortMode.None);
|
||||||
|
foreach (var g in all)
|
||||||
|
{
|
||||||
|
if (g == null || !g.enabled) continue;
|
||||||
|
if (g.transform.IsChildOf(paper)) continue;
|
||||||
|
g.enabled = false;
|
||||||
|
disabled.Add(g);
|
||||||
|
}
|
||||||
|
|
||||||
return disabled;
|
return disabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,7 +157,6 @@ namespace Darkmatter.Services.Capture
|
|||||||
|
|
||||||
var corners = new Vector3[4];
|
var corners = new Vector3[4];
|
||||||
rt.GetWorldCorners(corners);
|
rt.GetWorldCorners(corners);
|
||||||
// ScreenSpaceOverlay canvas: world corners are already in screen pixels.
|
|
||||||
float minX = Mathf.Clamp(corners[0].x, 0, screenW);
|
float minX = Mathf.Clamp(corners[0].x, 0, screenW);
|
||||||
float minY = Mathf.Clamp(corners[0].y, 0, screenH);
|
float minY = Mathf.Clamp(corners[0].y, 0, screenH);
|
||||||
float maxX = Mathf.Clamp(corners[2].x, 0, screenW);
|
float maxX = Mathf.Clamp(corners[2].x, 0, screenW);
|
||||||
|
|||||||
8
Assets/Darkmatter/Code/Services/Music.meta
Normal file
8
Assets/Darkmatter/Code/Services/Music.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 27c2e817c276c40b19f6284b7c87860c
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
8
Assets/Darkmatter/Code/Services/Music/Installers.meta
Normal file
8
Assets/Darkmatter/Code/Services/Music/Installers.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: e1d52cf6530064afd982535de65293bb
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using Darkmatter.Core.Contracts.Services.Music;
|
||||||
|
using Darkmatter.Libs.Installers;
|
||||||
|
using Darkmatter.Services.Music.Systems;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.Audio;
|
||||||
|
using VContainer;
|
||||||
|
using VContainer.Unity;
|
||||||
|
|
||||||
|
namespace Darkmatter.Services.Music.Installers
|
||||||
|
{
|
||||||
|
public class MusicServiceModule : MonoBehaviour, IModule
|
||||||
|
{
|
||||||
|
[SerializeField] private AudioClip defaultTrack;
|
||||||
|
[SerializeField, Range(0f, 1f)] private float defaultVolume = 0.7f;
|
||||||
|
[SerializeField, Min(0f)] private float crossFadeSeconds = 0.4f;
|
||||||
|
[SerializeField] private AudioMixerGroup mixerGroup;
|
||||||
|
|
||||||
|
public void Register(IContainerBuilder builder)
|
||||||
|
{
|
||||||
|
builder.RegisterInstance(new MusicConfig(defaultTrack, defaultVolume, crossFadeSeconds, mixerGroup));
|
||||||
|
builder.RegisterEntryPoint<MusicService>(Lifetime.Singleton).As<IMusicService>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 47cce31d8e31341a0ae46684e87cbd95
|
||||||
8
Assets/Darkmatter/Code/Services/Music/Systems.meta
Normal file
8
Assets/Darkmatter/Code/Services/Music/Systems.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: bec4745668fea4987809c8370d372048
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
23
Assets/Darkmatter/Code/Services/Music/Systems/MusicConfig.cs
Normal file
23
Assets/Darkmatter/Code/Services/Music/Systems/MusicConfig.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.Audio;
|
||||||
|
|
||||||
|
namespace Darkmatter.Services.Music.Systems
|
||||||
|
{
|
||||||
|
[Serializable]
|
||||||
|
public readonly struct MusicConfig
|
||||||
|
{
|
||||||
|
public AudioClip DefaultTrack { get; }
|
||||||
|
public float DefaultVolume { get; }
|
||||||
|
public float CrossFadeSeconds { get; }
|
||||||
|
public AudioMixerGroup MixerGroup { get; }
|
||||||
|
|
||||||
|
public MusicConfig(AudioClip defaultTrack, float defaultVolume, float crossFadeSeconds, AudioMixerGroup mixerGroup)
|
||||||
|
{
|
||||||
|
DefaultTrack = defaultTrack;
|
||||||
|
DefaultVolume = defaultVolume;
|
||||||
|
CrossFadeSeconds = crossFadeSeconds;
|
||||||
|
MixerGroup = mixerGroup;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 4c15bf1ed26cf4c8f8339b22cb57efc6
|
||||||
146
Assets/Darkmatter/Code/Services/Music/Systems/MusicService.cs
Normal file
146
Assets/Darkmatter/Code/Services/Music/Systems/MusicService.cs
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using Cysharp.Threading.Tasks;
|
||||||
|
using Darkmatter.Core.Contracts.Services.Music;
|
||||||
|
using Darkmatter.Core.Data.Signals.Features.AppBoot;
|
||||||
|
using Darkmatter.Libs.Observer;
|
||||||
|
using UnityEngine;
|
||||||
|
using VContainer.Unity;
|
||||||
|
|
||||||
|
namespace Darkmatter.Services.Music.Systems
|
||||||
|
{
|
||||||
|
public class MusicService : IMusicService, IStartable, IDisposable
|
||||||
|
{
|
||||||
|
private readonly IEventBus _bus;
|
||||||
|
private readonly MusicConfig _config;
|
||||||
|
|
||||||
|
private GameObject _host;
|
||||||
|
private AudioSource _source;
|
||||||
|
private IDisposable _introSub;
|
||||||
|
private CancellationTokenSource _fadeCts;
|
||||||
|
|
||||||
|
public MusicService(IEventBus bus, MusicConfig config)
|
||||||
|
{
|
||||||
|
_bus = bus;
|
||||||
|
_config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Start()
|
||||||
|
{
|
||||||
|
_host = new GameObject("[MusicService]");
|
||||||
|
UnityEngine.Object.DontDestroyOnLoad(_host);
|
||||||
|
_source = _host.AddComponent<AudioSource>();
|
||||||
|
_source.playOnAwake = false;
|
||||||
|
_source.loop = true;
|
||||||
|
_source.spatialBlend = 0f;
|
||||||
|
_source.volume = _config.DefaultVolume;
|
||||||
|
if (_config.MixerGroup != null) _source.outputAudioMixerGroup = _config.MixerGroup;
|
||||||
|
|
||||||
|
_introSub = _bus.Subscribe<IntroCompletedSignal>(OnIntroCompleted);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnIntroCompleted(IntroCompletedSignal _)
|
||||||
|
{
|
||||||
|
if (_config.DefaultTrack != null && (_source == null || !_source.isPlaying))
|
||||||
|
Play(_config.DefaultTrack, _config.DefaultVolume, loop: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Play(AudioClip clip, float volume01 = 1f, bool loop = true)
|
||||||
|
{
|
||||||
|
if (clip == null || _source == null) return;
|
||||||
|
CancelFade();
|
||||||
|
|
||||||
|
if (_source.isPlaying && _config.CrossFadeSeconds > 0f)
|
||||||
|
{
|
||||||
|
_fadeCts = new CancellationTokenSource();
|
||||||
|
CrossFadeAsync(clip, Mathf.Clamp01(volume01), loop, _fadeCts.Token).Forget();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_source.clip = clip;
|
||||||
|
_source.loop = loop;
|
||||||
|
_source.volume = Mathf.Clamp01(volume01);
|
||||||
|
_source.Play();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Stop(float fadeOutSeconds = 0f)
|
||||||
|
{
|
||||||
|
if (_source == null) return;
|
||||||
|
CancelFade();
|
||||||
|
if (fadeOutSeconds <= 0f) { _source.Stop(); return; }
|
||||||
|
_fadeCts = new CancellationTokenSource();
|
||||||
|
FadeOutAsync(fadeOutSeconds, _fadeCts.Token).Forget();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetVolume(float volume01)
|
||||||
|
{
|
||||||
|
if (_source == null) return;
|
||||||
|
_source.volume = Mathf.Clamp01(volume01);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async UniTaskVoid CrossFadeAsync(AudioClip nextClip, float targetVolume, bool loop, CancellationToken ct)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
float start = _source.volume;
|
||||||
|
float t = 0f;
|
||||||
|
float dur = _config.CrossFadeSeconds;
|
||||||
|
while (t < dur)
|
||||||
|
{
|
||||||
|
ct.ThrowIfCancellationRequested();
|
||||||
|
t += Time.unscaledDeltaTime;
|
||||||
|
_source.volume = Mathf.Lerp(start, 0f, t / dur);
|
||||||
|
await UniTask.Yield(PlayerLoopTiming.Update, ct);
|
||||||
|
}
|
||||||
|
_source.Stop();
|
||||||
|
_source.clip = nextClip;
|
||||||
|
_source.loop = loop;
|
||||||
|
_source.volume = 0f;
|
||||||
|
_source.Play();
|
||||||
|
t = 0f;
|
||||||
|
while (t < dur)
|
||||||
|
{
|
||||||
|
ct.ThrowIfCancellationRequested();
|
||||||
|
t += Time.unscaledDeltaTime;
|
||||||
|
_source.volume = Mathf.Lerp(0f, targetVolume, t / dur);
|
||||||
|
await UniTask.Yield(PlayerLoopTiming.Update, ct);
|
||||||
|
}
|
||||||
|
_source.volume = targetVolume;
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
private async UniTaskVoid FadeOutAsync(float seconds, CancellationToken ct)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
float start = _source.volume;
|
||||||
|
float t = 0f;
|
||||||
|
while (t < seconds)
|
||||||
|
{
|
||||||
|
ct.ThrowIfCancellationRequested();
|
||||||
|
t += Time.unscaledDeltaTime;
|
||||||
|
_source.volume = Mathf.Lerp(start, 0f, t / seconds);
|
||||||
|
await UniTask.Yield(PlayerLoopTiming.Update, ct);
|
||||||
|
}
|
||||||
|
_source.Stop();
|
||||||
|
_source.volume = start;
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CancelFade()
|
||||||
|
{
|
||||||
|
_fadeCts?.Cancel();
|
||||||
|
_fadeCts?.Dispose();
|
||||||
|
_fadeCts = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
CancelFade();
|
||||||
|
_introSub?.Dispose();
|
||||||
|
if (_host != null) UnityEngine.Object.Destroy(_host);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 8b4c07c26f9a44409bd43d2777b9951d
|
||||||
8
Assets/Darkmatter/Content/Audio.meta
Normal file
8
Assets/Darkmatter/Content/Audio.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 854202d005e754405862e01141384a81
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ed4a54b01b78fa1489c9baf442235e8a
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
Congrats! Now you have the FREE CASUAL GAME SFX
|
||||||
|
This is the FREE SFX solucion for your casual games!
|
||||||
|
*designed by Matheus Klein.
|
||||||
|
|
||||||
|
------>>>>> Do you want a GIFT? <<<<<------------
|
||||||
|
|
||||||
|
Subscribe here and download it:
|
||||||
|
https://lp.labmdk.com/free-gift
|
||||||
|
|
||||||
|
------>>>>> <<<<<------------
|
||||||
|
Leave for us your Review!
|
||||||
|
|
||||||
|
------>>>>> <<<<<------------
|
||||||
|
Check It out:
|
||||||
|
--> Casual-pack-SFX
|
||||||
|
https://assetstore.unity.com/packages/audio/sound-fx/casual-pack-sfx-192848?_ga=2.42343461.1670194729.1622553216-1748727751.1620872230
|
||||||
|
|
||||||
|
--> Casual-pack- MUSIC
|
||||||
|
https://assetstore.unity.com/packages/audio/music/casual-pack-music-193478?_ga=2.42343461.1670194729.1622553216-1748727751.1620872230
|
||||||
|
|
||||||
|
--> Casual-pack-COMPLETE
|
||||||
|
https://assetstore.unity.com/packages/audio/music/casual-pack-complete-196775
|
||||||
|
|
||||||
|
------>>>>> <<<<<------------
|
||||||
|
--> We created this product with enthusiasm and dedication. Enjoy it!
|
||||||
|
|
||||||
|
--> If you have any problem or if you are searching for customized assets
|
||||||
|
feel free to contact us:
|
||||||
|
|
||||||
|
- www.labmdk.com
|
||||||
|
- contato@labmdk.com
|
||||||
|
|
||||||
|
|
||||||
|
-->For more, follow us:
|
||||||
|
|
||||||
|
@matheus_klein.mk
|
||||||
|
@3dk_art
|
||||||
|
|
||||||
|
---> Looking for an original soundtrack for your custom project?
|
||||||
|
What can I offer?
|
||||||
|
|
||||||
|
Composing exclusive music, soundtrack, filmscore for film, video game, trailers, apps and advertisement.
|
||||||
|
Any music genre: cinematic, orchestral, Rock, metal, jazz, hip-hop, EDM, ambient, 8 bit etc.
|
||||||
|
Realistic sound. I work with virtual instruments and some real instruments.
|
||||||
|
|
||||||
|
We're looking forward to working with you again.
|
||||||
|
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 8dc69ef1d5b95dd48b0e5599b3de71d0
|
||||||
|
TextScriptImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
AssetOrigin:
|
||||||
|
serializedVersion: 1
|
||||||
|
productId: 197054
|
||||||
|
packageName: FREE CASUAL PACK SFX
|
||||||
|
packageVersion: 2.0
|
||||||
|
assetPath: Assets/1.1 - FREE CASUAL PACK SFX/(README) WELCOME_FREE CASUAL PACK
|
||||||
|
SFX.txt
|
||||||
|
uploadId: 550051
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 9b5dca4b9b9435b468d27e1d658c8b7c
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Binary file not shown.
@@ -0,0 +1,29 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: cbd5ee476a488444aaeb06b7e1fb04e0
|
||||||
|
AudioImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 6
|
||||||
|
defaultSettings:
|
||||||
|
loadType: 0
|
||||||
|
sampleRateSetting: 0
|
||||||
|
sampleRateOverride: 44100
|
||||||
|
compressionFormat: 1
|
||||||
|
quality: 1
|
||||||
|
conversionMode: 0
|
||||||
|
platformSettingOverrides: {}
|
||||||
|
forceToMono: 0
|
||||||
|
normalize: 1
|
||||||
|
preloadAudioData: 1
|
||||||
|
loadInBackground: 0
|
||||||
|
ambisonic: 0
|
||||||
|
3D: 1
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
AssetOrigin:
|
||||||
|
serializedVersion: 1
|
||||||
|
productId: 197054
|
||||||
|
packageName: FREE CASUAL PACK SFX
|
||||||
|
packageVersion: 2.0
|
||||||
|
assetPath: Assets/1.1 - FREE CASUAL PACK SFX/10 - BONUS/Casual loop 1.wav
|
||||||
|
uploadId: 550051
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 003fad9ceeb31b44c9618baec1eed419
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Binary file not shown.
@@ -0,0 +1,29 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 36a97d152a07e9f489c73b24e2870cf0
|
||||||
|
AudioImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 6
|
||||||
|
defaultSettings:
|
||||||
|
loadType: 0
|
||||||
|
sampleRateSetting: 0
|
||||||
|
sampleRateOverride: 44100
|
||||||
|
compressionFormat: 1
|
||||||
|
quality: 1
|
||||||
|
conversionMode: 0
|
||||||
|
platformSettingOverrides: {}
|
||||||
|
forceToMono: 0
|
||||||
|
normalize: 1
|
||||||
|
preloadAudioData: 1
|
||||||
|
loadInBackground: 0
|
||||||
|
ambisonic: 0
|
||||||
|
3D: 1
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
AssetOrigin:
|
||||||
|
serializedVersion: 1
|
||||||
|
productId: 197054
|
||||||
|
packageName: FREE CASUAL PACK SFX
|
||||||
|
packageVersion: 2.0
|
||||||
|
assetPath: Assets/1.1 - FREE CASUAL PACK SFX/CAMERA CLICKS/CAMERA CLICK 1.wav
|
||||||
|
uploadId: 550051
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 394f992e39409a246ad69e88caf01d6c
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Binary file not shown.
@@ -0,0 +1,29 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 4b5dbd3527221224caf2fde9d72cddb7
|
||||||
|
AudioImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 6
|
||||||
|
defaultSettings:
|
||||||
|
loadType: 0
|
||||||
|
sampleRateSetting: 0
|
||||||
|
sampleRateOverride: 44100
|
||||||
|
compressionFormat: 1
|
||||||
|
quality: 1
|
||||||
|
conversionMode: 0
|
||||||
|
platformSettingOverrides: {}
|
||||||
|
forceToMono: 0
|
||||||
|
normalize: 1
|
||||||
|
preloadAudioData: 1
|
||||||
|
loadInBackground: 0
|
||||||
|
ambisonic: 0
|
||||||
|
3D: 1
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
AssetOrigin:
|
||||||
|
serializedVersion: 1
|
||||||
|
productId: 197054
|
||||||
|
packageName: FREE CASUAL PACK SFX
|
||||||
|
packageVersion: 2.0
|
||||||
|
assetPath: Assets/1.1 - FREE CASUAL PACK SFX/CARDS/CARDS 1.wav
|
||||||
|
uploadId: 550051
|
||||||
Binary file not shown.
@@ -0,0 +1,29 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 4f3fe7a1f92f1e94db61865f48491057
|
||||||
|
AudioImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 6
|
||||||
|
defaultSettings:
|
||||||
|
loadType: 0
|
||||||
|
sampleRateSetting: 0
|
||||||
|
sampleRateOverride: 44100
|
||||||
|
compressionFormat: 1
|
||||||
|
quality: 1
|
||||||
|
conversionMode: 0
|
||||||
|
platformSettingOverrides: {}
|
||||||
|
forceToMono: 0
|
||||||
|
normalize: 1
|
||||||
|
preloadAudioData: 1
|
||||||
|
loadInBackground: 0
|
||||||
|
ambisonic: 0
|
||||||
|
3D: 1
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
AssetOrigin:
|
||||||
|
serializedVersion: 1
|
||||||
|
productId: 197054
|
||||||
|
packageName: FREE CASUAL PACK SFX
|
||||||
|
packageVersion: 2.0
|
||||||
|
assetPath: Assets/1.1 - FREE CASUAL PACK SFX/CARDS/CARDS 2.wav
|
||||||
|
uploadId: 550051
|
||||||
Binary file not shown.
@@ -0,0 +1,14 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 2a78ea6479568174996fe4ab6d489c16
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
AssetOrigin:
|
||||||
|
serializedVersion: 1
|
||||||
|
productId: 197054
|
||||||
|
packageName: FREE CASUAL PACK SFX
|
||||||
|
packageVersion: 2.0
|
||||||
|
assetPath: Assets/1.1 - FREE CASUAL PACK SFX/CASUAL GAME SFX FREE_2.0.pdf
|
||||||
|
uploadId: 550051
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 77d40c2b43ea7704dad6d8c061256ca2
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user